package org.msh.tb.cases.unitview;

import org.apache.commons.collections.map.HashedMap;
import org.jboss.seam.Component;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.msh.tb.application.App;
import org.msh.tb.entities.*;
import org.msh.tb.entities.enums.*;
import org.msh.tb.ng.TreatmentInfoNg;
import org.msh.utils.date.Period;

import javax.persistence.EntityManager;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;


/**
 * Return information about cases on treatment
 * @author Mauricio Santos
 *
 */
@Name("treatmentsInfoHomeNg")
@BypassInterceptors
public class TreatmentsInfoHomeNg extends TreatmentsInfoHome {

    private static final String HQL_CONFIRMED = "select c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, " +
            "c.daysTreatPlanned, c.classification, c.patientType, p.gender, c.infectionSite, pt.name.name1, c.registrationDate, " +
            "(select ( sum(cdd.day1) + sum(cdd.day2) + sum(cdd.day3) + sum(cdd.day4) + sum(cdd.day5) + sum(cdd.day6) + sum(cdd.day7) + sum(cdd.day8) + sum(cdd.day9) " +
            "+ sum(cdd.day10) + sum(cdd.day11) + sum(cdd.day12) + sum(cdd.day13) + sum(cdd.day14) + sum(cdd.day15) + sum(cdd.day16) + sum(cdd.day17) + sum(cdd.day18) " +
            "+ sum(cdd.day19) + sum(cdd.day20) + sum(cdd.day21) + sum(cdd.day22) + sum(cdd.day23) + sum(cdd.day24) + sum(cdd.day25) + sum(cdd.day26) + sum(cdd.day27) " +
            "+ sum(cdd.day28) + sum(cdd.day29) + sum(cdd.day30) + sum(cdd.day31) ) as total " +
            "from CaseDispensing_Ng tm0 join tm0.dispensingDays cdd where tm0.tbcase.id = c.id) as medicineTakenDays " +
            "from TbCase c " +
            "join c.patient p left join c.pulmonaryType pt " +
            "where c.state <= " + CaseState.TRANSFERRING.ordinal() + " and c.diagnosisType = " + DiagnosisType.CONFIRMED.ordinal() +
            " and c.ownerUnit.id = :unitId " +
            "group by c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, c.daysTreatPlanned, c.classification ";

    private static final String HQL_SUSPECT = "select c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, " +
            "c.daysTreatPlanned, c.classification, c.patientType, p.gender, c.infectionSite, pt.name.name1, c.registrationDate " +
            "from TbCase c " +
            "join c.patient p left join c.pulmonaryType pt " +
            "where c.state <= " + CaseState.TRANSFERRING.ordinal() + " and c.diagnosisType = " + DiagnosisType.SUSPECT.ordinal() +
            " and c.ownerUnit.id = :unitId " +
            "group by c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, c.daysTreatPlanned, c.classification ";


    /**
     * Store the list of weights by on-going case
     */
    private Map<Integer, Double> caseWeights;

    private List<TreatmentHealthUnit> transferInCases;

    private Integer transferInCount;

    //protected CaseGroup suspectGroup;

    private CaseGroup tbSuspects;
    private CaseGroup drtbSuspects;


    /**
     * Return the number of transfer in cases of the unit
     * @return
     */
    public Integer getTransferInCount() {
        if (transferInCount == null) {
            Number num = (Number)App.getEntityManager().createQuery("select count(*) from TreatmentHealthUnit " +
                    "where tbunit.id = :id and transferring = true")
                    .setParameter("id", getTbunitId())
                    .getSingleResult();

            transferInCount = num.intValue();
        }

        return transferInCount;
    }

    public List<TreatmentHealthUnit> getTransferInCases() {
        return transferInCases;
    }


    public void loadTransferInCases() {
        EntityManager em = App.getEntityManager();

        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        List<TreatmentHealthUnit> lst = em
                .createQuery("from TreatmentHealthUnit a " +
                        "where a.tbcase.ownerUnit.id = :id " +
                        "and a.period.endDate = (select max(b.period.endDate) from TreatmentHealthUnit b " +
                        "where b.tbcase.id = a.tbcase.id and b.transferring = false) " +
                        "and exists(select id from TreatmentHealthUnit where tbcase.id = a.tbcase.id " +
                        "and transferring = true and tbunit.id = :id)")
                .setParameter("id", getTbunitId())
                .getResultList();

        transferInCases = lst;
    }


    public List<CaseGroup> getConfirmedGroups() {
        String HQL_CONFIRMEDTEST = "select c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, " +
                "c.daysTreatPlanned, c.classification, c.patientType, p.gender, c.infectionSite, pt.name.name1, c.registrationDate, " +
                "(select ( count(cdd.day1) + count(cdd.day2) + count(cdd.day3) + count(cdd.day4) + count(cdd.day5) + count(cdd.day6) + count(cdd.day7) + count(cdd.day8) + count(cdd.day9) " +
                "+ count(cdd.day10) + count(cdd.day11) + count(cdd.day12) + count(cdd.day13) + count(cdd.day14) + count(cdd.day15) + count(cdd.day16) + count(cdd.day17) + count(cdd.day18) " +
                "+ count(cdd.day19) + count(cdd.day20) + count(cdd.day21) + count(cdd.day22) + count(cdd.day23) + count(cdd.day24) + count(cdd.day25) + count(cdd.day26) + count(cdd.day27) " +
                "+ count(cdd.day28) + count(cdd.day29) + count(cdd.day30) + count(cdd.day31) ) as total " +
                "from CaseDispensing_Ng tm0 join tm0.dispensingDays cdd where tm0.tbcase.id = c.id) as medicineTakenDays " +
                "from TbCase c " +
                "join c.patient p left join c.pulmonaryType pt " +
                "where c.state <= " + CaseState.TRANSFERRING.ordinal() + " and c.diagnosisType = " + DiagnosisType.CONFIRMED.ordinal() +
                " and c.ownerUnit.id = :unitId " +
                "group by c.id, p.name, p.middleName, p.lastName, c.treatmentPeriod, c.daysTreatPlanned, c.classification ";


        if (super.groups == null || groups.size() == 0) {
            super.createTreatments(HQL_CONFIRMEDTEST);

            for (CaseGroup grp: super.groups) {
                for (TreatmentInfo info: grp.getTreatments()) {
                    Integer id = info.getCaseId();
                    info.setWeight(getCaseWeight(id));
                }
            }
        }
        return super.groups;
    }

    /**
     * Create list of DR-TB suspects
     * @return
     */
    public CaseGroup getDrtbSuspects() {
        if (drtbSuspects == null) {
            createSuspectTreatments();
        }
        return drtbSuspects;
    }

    /**
     * Return list of TB suspects
     * @return
     */
    public CaseGroup getTbSuspects() {
        if (tbSuspects == null) {
            createSuspectTreatments();
        }
        return tbSuspects;
    }


    public CaseGroup getConfirmedTBCasesGroup() {
        if (getConfirmedGroups() != null) {
            for (CaseGroup cg : getConfirmedGroups()) {
                if (cg.getClassification() == CaseClassification.TB) {
                    return cg;
                }
            }
        }
        return null;
    }

    public CaseGroup getConfirmedDRTBCasesGroup() {
        if (getConfirmedGroups() != null) {
            for (CaseGroup cg : getConfirmedGroups()) {
                if (cg.getClassification() == CaseClassification.DRTB) {
                    return cg;
                }
            }
        }
        return null;
    }

    /**
     * Create the list of treatments for the health unit TB unit
     */
    protected void createSuspectTreatments() {
        // get the unit id selected
        Tbunit unit = getTbunit();
        if (unit == null) {
            return;
        }

        groups = new ArrayList<CaseGroup>();

        List<Object[]> lst = App.getEntityManager().createQuery(HQL_SUSPECT + getHQLOrderBy())
                .setParameter("unitId", unit.getId())
                .getResultList();

        Patient p = new Patient();
        Workspace ws = (Workspace) Component.getInstance("defaultWorkspace", true);
        tbSuspects = new CaseGroup();
        drtbSuspects = new CaseGroup();

        for (Object[] vals: lst) {
            CaseClassification classification = (CaseClassification)vals[6];

            // select the group based on the classification
            CaseGroup grp = classification == CaseClassification.TB ? tbSuspects : drtbSuspects;

            PatientType pt = (PatientType)vals[7];
            Gender gender = (Gender)vals[8];
            InfectionSite is = (InfectionSite) vals[9];

            TreatmentInfo info = new TreatmentInfoNg();
            info.setCaseId((Integer)vals[0]);

            p.setName((String)vals[1]);
            p.setMiddleName((String)vals[2]);
            p.setLastName((String)vals[3]);
            p.setGender(gender);
            info.setPatientName(p.compoundName(ws));
            info.setPatientType(pt);
            info.setTreatmentPeriod((Period)vals[4]);
            if (vals[5] != null) {
                info.setNumDaysPlanned((Integer)vals[5]);
            }
            info.setInfectionSite(is);
            info.setPulmonaryType((String) vals[10]);
            info.setRegistrationDate((Date) vals[11]);

            // get weight
            info.setWeight(getCaseWeight(info.getCaseId()));

            grp.getTreatments().add(info);
        }

        loadCultureResults();
        loadMicroscopyResults();
        loadXpertResults();
    }


    /**
     * Simple way to return a list of exam results from the suspect cases
     * @param fields the list of fields to be included in the HQL sentence
     * @param entity the name of the entity to be included in the 'from' clause of the HQL sentence
     * @return list of objects, where the last item is the case number
     */
    protected List<Object[]> getExamResults(String fields, String entity) {
        // get the list of all suspects (tb and dr-tb)
        CaseGroup grp = getTbSuspects();
        List<TreatmentInfo> cases = new ArrayList<TreatmentInfo>(grp.getTreatments());
        cases.addAll(getDrtbSuspects().getTreatments());

        if (grp.getTreatments().size() == 0) {
            return null;
        }

        // create SQL query to return the exam result of the given case IDs
        String s = "";
        for (TreatmentInfo info: grp.getTreatments()) {
            TreatmentInfoNg item = (TreatmentInfoNg)info;
            if (!s.isEmpty()) {
                s += ",";
            }
            s += info.getCaseId();
        }
        String hql = "select " + fields + ", tbcase.id " +
                "from " + entity + " where tbcase.id in (" + s + ") " +
                "order by dateCollected";

        return App.getEntityManager().createQuery(hql).getResultList();
    }

    /**
     * Load information about xpert results of suspect cases
     */
    protected void loadXpertResults() {
        List<Object[]> lst = getExamResults("result, rifResult", "ExamXpert");

        if (lst == null) {
            return;
        }

        for (Object[] vals: lst) {
            XpertResult res = (XpertResult)vals[0];
            XpertRifResult rif = (XpertRifResult)vals[1];
            Integer caseId = (Integer)vals[2];

            TreatmentInfoNg info = findSuspect(caseId);

            if (info != null) {
                info.setXpertResult(res);
                info.setXpertRifResult(rif);
            }
        }
    }

    /**
     * Load information about culture result of suspect cases
     */
    protected void loadCultureResults() {
        List<Object[]> lst = getExamResults("result", "ExamCulture");

        if (lst == null) {
            return;
        }

        for (Object[] vals: lst) {
            CultureResult res = (CultureResult)vals[0];
            Integer caseId = (Integer)vals[1];

            TreatmentInfoNg info = findSuspect(caseId);
            if (info != null) {
                info.setCultureResult(res);
            }
        }
    }


    /**
     * Find a suspect by its case ID
     * @param caseId
     * @return
     */
    private TreatmentInfoNg findSuspect(int caseId) {
        TreatmentInfoNg info = (TreatmentInfoNg)getTbSuspects().findByCaseId(caseId);
        return info != null ?
                info :
                (TreatmentInfoNg)getDrtbSuspects().findByCaseId(caseId);
    }

    /**
     * Load information about microscopy result of suspect cases
     */
    protected void loadMicroscopyResults() {
        List<Object[]> lst = getExamResults("result", "ExamMicroscopy");

        if (lst == null) {
            return;
        }

        for (Object[] vals: lst) {
            MicroscopyResult res = (MicroscopyResult)vals[0];
            Integer caseId = (Integer)vals[1];

            TreatmentInfoNg info = findSuspect(caseId);
            if (info != null) {
                info.setMicroscopyResult(res);
            }
        }
    }


    @Override
    public Tbunit getTbunit() {
        return ((TreatmentsInfoHome)App.getComponentFromDefaultWorkspaceOrGeneric("treatmentsInfoHome")).getTbunit();
    }

    /**
     * Search the patient weight by its case ID
     * @param id
     * @return
     */
    private Double getCaseWeight(Integer id) {
        if (caseWeights == null) {
            loadCaseWeights();
        }

        return caseWeights.get(id);
    }

    /**
     * Load the cases weight
     */
    private void loadCaseWeights() {
        EntityManager em = (EntityManager) Component.getInstance("entityManager");

        List<Object[]> lst = em.createNativeQuery("select a.id, b.weight\n" +
                "from tbcase a\n" +
                "inner join medicalexamination b on b.case_id = a.id\n" +
                "where b.event_date = (select max(c.event_date) from medicalexamination c\n" +
                "where c.case_id = a.id and c.weight is not null) " +
                "and a.owner_unit_id = :unitid and b.weight is not null")
                .setParameter("unitid", getTbunitId())
                .getResultList();

        caseWeights = new HashedMap();

        for (Object[] vals: lst) {
            Number weight = (Number)vals[1];
            caseWeights.put((Integer)vals[0], weight.doubleValue());
        }
    }
}
