package org.msh.tb.entities;

import org.msh.etbm.commons.transactionlog.mapping.PropertyLog;
import org.msh.tb.entities.enums.CaseClassification;
import org.msh.tb.entities.enums.RegimenPhase;

import javax.persistence.*;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;



@Entity
@Table(name = "regimen")
public class Regimen extends WSObject implements Serializable, SyncKey {
    private static final long serialVersionUID = -8612534215100619569L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(length = 100, name = "regimen_name")
    private String name;

    private CaseClassification caseClassification;

    /**
     * The initial weight this regimen supports
     */
    private Double iniWeight;

    /**
     * The final weight this regimen supports
     */
    private Double endWeight;

    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "regimen", fetch = FetchType.LAZY)
    @PropertyLog(alwaysLog = true)
    private List<MedicineRegimen> medicines = new ArrayList<MedicineRegimen>();

    @Transient
    private List<MedicineRegimen> intensivePhaseMedicines = null;
    @Transient
    private List<MedicineRegimen> continuousPhaseMedicines = null;

    @Column(length = 50)
    @PropertyLog(messageKey = "global.legacyId")
    private String legacyId;

    @Transient
    // Ricardo: TEMPORARY UNTIL A SOLUTION IS FOUND. Just to attend a request from the XML data model to
    // map an XML node to a property in the model
    private Integer clientId;

    /**
     * Check if the regimen is eligible for the given weight
     * @param weight
     * @return
     */
    public boolean isEligible(Double weight) {
        if (weight == null || (iniWeight == null && endWeight == null)) {
            return true;
        }

        if (iniWeight == null) {
            return weight <= endWeight;
        }

        if (endWeight == null) {
            return weight >= iniWeight;
        }

        return weight >= iniWeight && weight <= endWeight;
    }

    /**
     * Return the weight range in a displayable format
     * @return
     */
    public String getWeightRangeDisplay() {
        DecimalFormat f = new DecimalFormat("#.#");

        if (iniWeight == null && endWeight == null) {
            return null;
        }

        if (endWeight == null) {
            return "≥ " + f.format(iniWeight) + " Kg";
        }

        if (iniWeight == null) {
            return "≤ " + f.format(endWeight) + " Kg";
        }

        return f.format(iniWeight) + "-" + f.format(endWeight) + " Kg";
    }

    @Override
    public Integer getClientId() {
        return clientId;
    }

    @Override
    public void setClientId(Integer clientId) {
        this.clientId = clientId;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;

        if (!(obj instanceof Regimen))
            return false;

        return ((Regimen)obj).getId().equals(getId());
    }


    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return (name != null? name: super.toString());
    }


    /**
     * Return the number of months of the intensive phase
     * @return
     */
    public int getMonthsIntensivePhase() {
        return getMonthsPhase(RegimenPhase.INTENSIVE);
    }


    /**
     * Return the number of months of the continuous phase
     * @return
     */
    public int getMonthsContinuousPhase() {
        return getMonthsPhase(RegimenPhase.CONTINUOUS);
    }


    /**
     * Include a medicine regimen to the regimen and also update the intensive and continuous list
     * @param mr
     */
    public void addMedicine(MedicineRegimen mr) {
        getMedicines().add(mr);
        if (intensivePhaseMedicines != null) {
            if (mr.getPhase() == RegimenPhase.INTENSIVE)
                intensivePhaseMedicines.add(mr);
            else continuousPhaseMedicines.add(mr);
        }
    }

    /**
     * Remove a medicine in the regimen and also update the intensive and continuous phase list
     * @param mr
     */
    public void remMedicine(MedicineRegimen mr) {
        getMedicines().remove(mr);
        if (intensivePhaseMedicines != null) {
            if (mr.getPhase() == RegimenPhase.INTENSIVE)
                intensivePhaseMedicines.remove(mr);
            else continuousPhaseMedicines.remove(mr);
        }
    }


    /**
     * get medicines used in the intensive phase
     * @return
     */
    public List<MedicineRegimen> getIntensivePhaseMedicines() {
        if (intensivePhaseMedicines == null)
            createPhaseLists();
        return intensivePhaseMedicines;
    }


    /**
     * Check if medicines are the same as medicines in an specific phase
     * @param phase phase to check
     * @param meds medicines to compare if they are in the regimen (or are compatible)
     * @return true if medicines are the same as in the regimen phase, otherwise false
     */
    public boolean compareMedicinesInPhase(RegimenPhase phase, List<Medicine> meds) {
        List<MedicineRegimen> lst;
        if (RegimenPhase.INTENSIVE.equals(phase))
            lst = getIntensivePhaseMedicines();
        else lst = getContinuousPhaseMedicines();

        if (lst.size() != meds.size())
            return false;

        for (MedicineRegimen mr: lst) {
            if (!meds.contains(mr.getMedicine()))
                return false;
        }

        return true;
    }


    /**
     * get medicines used in the continuous phase
     * @return
     */
    public List<MedicineRegimen> getContinuousPhaseMedicines() {
        if (continuousPhaseMedicines == null)
            createPhaseLists();
        return continuousPhaseMedicines;
    }

    private void createPhaseLists() {
        continuousPhaseMedicines = new ArrayList<MedicineRegimen>();
        intensivePhaseMedicines = new ArrayList<MedicineRegimen>();
        for (MedicineRegimen mr: getMedicines()) {
            if (mr.getPhase() == RegimenPhase.INTENSIVE)
                intensivePhaseMedicines.add(mr);
            else continuousPhaseMedicines.add(mr);
        }
    }


    /**
     * Create a list with different months of treatment for the medicines in a regimen phase
     * @param phase
     * @return
     */
    public List<Integer> groupMonthsTreatment(RegimenPhase phase) {
        List<Integer> months = new ArrayList<Integer>();
        for (MedicineRegimen medreg: getMedicines()) {
            if (medreg.getPhase() == phase) {
                Integer num = medreg.getMonthsTreatment();
                if ((num != null) && (!months.contains(num)))
                    months.add(num);
            }

            Collections.sort(months, new Comparator<Integer>() {
                public int compare(Integer o1, Integer o2) {
                    if (o1 < o2)
                        return -1;
                    else if (o1 > o2)
                        return 1;
                    return 0;
                }
            });
        }
        return months;
    }


    /**
     * Create a list of {@link MedicineRegimen} from an specific phase with a number of months of treatment
     * @param phase
     * @param months
     * @return
     */
    public List<MedicineRegimen> groupMedicinesByMonthTreatment(RegimenPhase phase, int months) {
        List<MedicineRegimen> lst = new ArrayList<MedicineRegimen>();
        for (MedicineRegimen medreg: getMedicines()) {
            if (medreg.getPhase() == phase) {
                if ( medreg.getMonthsTreatment() >= months ) {
                    lst.add(medreg);
                }
            }
        }
        return lst;
    }


    /**
     * Return number of months of the phase
     * @param phase
     * @return
     */
    public int getMonthsPhase(RegimenPhase phase) {
        int num=0;
        for (MedicineRegimen medreg: getMedicines()) {
            if ((medreg.getMonthsTreatment() > num) && (medreg.getPhase().equals(phase))) {
                if (medreg.getMonthsTreatment() > num)
                    num = medreg.getMonthsTreatment();
            }
        }
        return num;
    }


    /**
     * Check if medicine is part of the regimen
     * @param med
     * @return
     */
    public boolean isMedicineInRegimen(Medicine med) {
        for (MedicineRegimen aux: getMedicines()) {
            if (aux.getMedicine().equals(med)) {
                return true;
            }
        }
        return false;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<MedicineRegimen> getMedicines() {
        return medicines;
    }

    public void setMedicines(List<MedicineRegimen> medicines) {
        this.medicines = medicines;
    }


    /**
     * @return the mdrTreatment
     */
    public boolean isMdrTreatment() {
        return CaseClassification.DRTB.equals(caseClassification);
    }


    /**
     * @return the tbTreatment
     */
    public boolean isTbTreatment() {
        return CaseClassification.TB.equals(caseClassification);
    }


    /**
     * @return the legacyId
     */
    public String getLegacyId() {
        return legacyId;
    }


    /**
     * @param legacyId the legacyId to set
     */
    public void setLegacyId(String legacyId) {
        this.legacyId = legacyId;
    }


    /**
     * @return the caseClassification
     */
    public CaseClassification getCaseClassification() {
        return caseClassification;
    }


    /**
     * @param caseClassification the caseClassification to set
     */
    public void setCaseClassification(CaseClassification caseClassification) {
        this.caseClassification = caseClassification;
    }

    public Double getIniWeight() {
        return iniWeight;
    }

    public void setIniWeight(Double iniWeight) {
        this.iniWeight = iniWeight;
    }

    public Double getEndWeight() {
        return endWeight;
    }

    public void setEndWeight(Double endWeight) {
        this.endWeight = endWeight;
    }
}
