package org.msh.tb.adminunits;

import org.jboss.seam.Component;
import org.jboss.seam.faces.FacesMessages;
import org.msh.tb.entities.AdministrativeUnit;
import org.msh.tb.entities.UserLogin;
import org.msh.tb.entities.UserWorkspace;
import org.msh.tb.entities.Workspace;
import org.msh.tb.entities.enums.UserView;

import javax.persistence.EntityManager;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper class to allow users to select an administrative unit
 * @author Ricardo Memoria
 *
 */
public class AdminUnitSelection {

    private AdministrativeUnit[] units = new AdministrativeUnit[5];
    private String[] labelLevel = new String[5];

    private boolean alreadySelected;
    private List<AdministrativeUnit> unitsList;

    private boolean applyUserRestrictions;
    //	private UserWorkspace userWorkspace;
    private List<AdminUnitChangeListener> listeners;
    private Integer patientAddrRequiredLevels;
    //	private Workspace defaultWorkspace;


    /**
     * Constructor with default argument
     * @param applyUserRestrictions if true, the selection will apply the restrictions of user view
     */
    public AdminUnitSelection(boolean applyUserRestrictions) {
        super();
        this.applyUserRestrictions = applyUserRestrictions;
        if (applyUserRestrictions) {
            selectUserRestrictions();
        }
        initializeAddrLevel();
    }

    /**
     * Default constructor
     */
    public AdminUnitSelection() {
        super();
    }

    public AdministrativeUnit checkRequired() {
        AdministrativeUnit aux = getSelectedUnit();
        if (aux == null) {
            FacesMessages facesMessages = (FacesMessages)Component.getInstance("facesMessages", true);
            facesMessages.addToControlFromResourceBundle("cbselau1", "javax.faces.component.UIInput.REQUIRED");
        }
        return aux;
    }

    /**
     * Get the selected unit, i.e., the unit at the end point of the selected tree
     * @return instance of {@link AdministrativeUnit}
     */
    public AdministrativeUnit getSelectedUnit() {
        for (int i = 4; i >= 0; i--) {
            if (units[i] != null) {
                return units[i];
            }
        }

        return null;
    }


    protected void initializeAddrLevel() {
        Workspace defaultWorkspace = (Workspace)Component.getInstance("defaultWorkspace", true);
        if (defaultWorkspace == null) {
            throw new RuntimeException("No default workspace found");
        }
        patientAddrRequiredLevels = defaultWorkspace.getPatientAddrRequiredLevels();
    }

    /**
     * Select the unit and fill the nodes with the parent units
     * @param unit to be selected
     */
    public void setSelectedUnit(AdministrativeUnit unit) {
        alreadySelected = true;

        // clear the array
        for (int i = 0; i < 5; i++) {
            units[i] = null;
        }

        if (unit == null) {
            return;
        }

        // calculates the level of the unit
        AdministrativeUnit aux = unit;
        int level = 1;
        while (aux.getParent() != null) {
            aux = aux.getParent();
            level++;
        }

        aux = unit;
        for (int i = level; i > 0; i--) {
            units[i - 1] = aux;
            aux = aux.getParent();
        }
        notifyChange();
    }


    /**
     * Return the administrative level of the user
     * @return 0 if there is no restriction, otherwise checks user level restriction
     */
    public int getUserLevel() {
        if (!applyUserRestrictions) {
            return 0;
        }

        UserWorkspace userWorkspace = getUserWorkspace();
        if (userWorkspace.getView() == UserView.COUNTRY) {
            return 0;
        }

        if (userWorkspace.getView() == UserView.ADMINUNIT) {
            return userWorkspace.getAdminUnit().getLevel();
        }

        if (userWorkspace.getView() == UserView.TBUNIT) {
            return userWorkspace.getTbunit().getAdminUnit().getLevel();
        }

        return 0;
    }


    /**
     * Return the {@link UserLogin} component instance in the SEAM session context
     * @return {@link UserLogin} instance
     */
    public UserWorkspace getUserWorkspace() {
        return (UserWorkspace)Component.getInstance("userWorkspace", true);
    }

    /**
     * Return the label for first level of the country administrative organization
     * @return display name of the administrative unit
     */
    public String getLabelLevel1() {
        return getLabelLevel(1);
    }


    /**
     * Return the label for second level of the country administrative organization
     * @return display name of the administrative unit
     */
    public String getLabelLevel2() {
        return getLabelLevel(2);
    }


    /**
     * Return the label for third level of the country administrative organization
     * @return display name of the administrative unit
     */
    public String getLabelLevel3() {
        return getLabelLevel(3);
    }


    /**
     * Return the label for forth level of the country administrative organization
     * @return display name of the administrative unit
     */
    public String getLabelLevel4() {
        return getLabelLevel(4);
    }


    /**
     * Return the label for fifth level of the country administrative organization
     * @return display name of the administrative unit
     */
    public String getLabelLevel5() {
        return getLabelLevel(5);
    }



    /**
     * @return the level1
     */
    public AdministrativeUnit getUnitLevel1() {
        return units[0];
    }



    /**
     * @param level1 the level1 to set
     */
    public void setUnitLevel1(AdministrativeUnit level1) {
        units[0] = level1;
        clearUnits(2);
        notifyChange();
    }


    public Integer getUnitIdLevel1() {
        return (units[0] != null ? units[0].getId() : null);
    }

    public void setUnitIdLevel1(Integer id) {
        EntityManager em = getEntityManager();
        units[0] = em.find(AdministrativeUnit.class, id);
    }

    /**
     * @return the level2
     */
    public AdministrativeUnit getUnitLevel2() {
        return units[1];
    }


    /**
     * @param level2 the level2 to set
     */
    public void setUnitLevel2(AdministrativeUnit level2) {
        units[1] = level2;
        clearUnits(3);
        notifyChange();
    }


    /**
     * @return the level3
     */
    public AdministrativeUnit getUnitLevel3() {
        return units[2];
    }


    /**
     * @param level3 the level3 to set
     */
    public void setUnitLevel3(AdministrativeUnit level3) {
        units[2] = level3;
        clearUnits(4);
        notifyChange();
    }


    /**
     * @return the level4
     */
    public AdministrativeUnit getUnitLevel4() {
        return units[3];
    }


    /**
     * @param level4 the level4 to set
     */
    public void setUnitLevel4(AdministrativeUnit level4) {
        units[3] = level4;
        clearUnits(5);
        notifyChange();
    }


    /**
     * @return the level5
     */
    public AdministrativeUnit getUnitLevel5() {
        return units[4];
    }


    /**
     * @param level5 the level5 to set
     */
    public void setUnitLevel5(AdministrativeUnit level5) {
        units[4] = level5;
        clearUnits(6);
        notifyChange();
    }


    /**
     * Return the administrative name for a given name of a specific parent administrative unit
     * @param level
     * @return
     */
    public String getLabelLevel(int level) {
        // creates the component responsible for generating the list
        AdminUnitSelectionList aulist = getAdminUnitSelectionList();

        // level name is blank ?
        if (labelLevel[level - 1] == null) {
            List<AdministrativeUnit> lst = null;
            switch (level) {
                case 1: lst = aulist.getUnitsLevel1();
                    break;
                case 2: lst = aulist.getUnitsLevel2();
                    break;
                case 3: lst = aulist.getUnitsLevel3();
                    break;
                case 4: lst = aulist.getUnitsLevel4();
                    break;
                case 5: lst = aulist.getUnitsLevel5();
                    break;
            }

            if (lst == null) {
                return null;
            }

            List<String> names = new ArrayList<String>();

            for (AdministrativeUnit adm: lst) {
                String s = adm.getCountryStructure().getName().toString();

                if (!names.contains(s)) {
                    names.add(s);
                }
            }

            String txt = "";
            for (String name: names) {
                if (txt.length() > 0) {
                    txt += " / ";
                }
                txt += name;
            }
            labelLevel[level - 1] = txt;
        }


        return labelLevel[level - 1];
    }


    protected AdminUnitSelectionList getAdminUnitSelectionList() {
        return ((AdminListWrapper)Component.getInstance("adminListWrapper", true)).getReference(this);
    }


    /**
     * Return the list of {@link AdministrativeUnit} options for level 1 to be selected by the user
     * @return List of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getOptionsLevel1() {
        if (isLevelReadOnly(1)) {
            return null;
        }

        return getAdminUnitSelectionList().getUnitsLevel1();
    }


    /**
     * Return the list of {@link AdministrativeUnit} options for level 2 to be selected by the user
     * @return List of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getOptionsLevel2() {
        if (isLevelReadOnly(2)) {
            return null;
        }
        return getAdminUnitSelectionList().getUnitsLevel2();
    }


    /**
     * Return the list of {@link AdministrativeUnit} options for level 3 to be selected by the user
     * @return List of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getOptionsLevel3() {
        if (isLevelReadOnly(3))
            return null;
        return getAdminUnitSelectionList().getUnitsLevel3();
    }


    /**
     * Return the list of {@link AdministrativeUnit} options for level 4 to be selected by the user
     * @return List of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getOptionsLevel4() {
        if (isLevelReadOnly(4)) {
            return null;
        }
        return getAdminUnitSelectionList().getUnitsLevel4();
    }


    /**
     * Return the list of {@link AdministrativeUnit} options for level 5 to be selected by the user
     * @return List of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getOptionsLevel5() {
        if (isLevelReadOnly(5)) {
            return null;
        }

        return getAdminUnitSelectionList().getUnitsLevel5();
    }

    /**
     * @return the alreadSelected
     */
    public boolean isAlreadySelected() {
        return alreadySelected;
    }


    /**
     * Clear the lower level units
     * @param level
     */
    protected void clearUnits(int level) {
        for (int i = level - 1; i < 5; i++) {
            labelLevel[i] = null;
            units[i] = null;
        }
        unitsList = null;
    }


    protected EntityManager getEntityManager() {
        return (EntityManager)Component.getInstance("entityManager");
    }

    /**
     * Return the list of parent units and the selected one
     * @return {@link List} of {@link AdministrativeUnit} instances
     */
    public List<AdministrativeUnit> getParentUnits() {
        unitsList = new ArrayList<AdministrativeUnit>();
        for (int i = 0; i < 5; i++) {
            if (units[i] == null) {
                break;
            }
            unitsList.add(units[i]);
        }
        return unitsList;
    }


    /**
     * Load the list of administrative units based on the user view
     */
    protected void mountUserTree() {
        UserWorkspace uw = getUserWorkspace();
        if (uw.getView() == UserView.COUNTRY) {
            return;
        }

        AdministrativeUnit adm = null;
        if (uw.getView() == UserView.ADMINUNIT) {
            adm = uw.getAdminUnit();
        }

        if (uw.getView() == UserView.TBUNIT) {
            adm = uw.getTbunit().getAdminUnit();
        }

        List<AdministrativeUnit> lst = adm.getParentsTreeList(true);
        for (int i = 0; i < 5; i++) {
            units[i] = i < lst.size() ? lst.get(i) : null;
        }
    }


    /**
     * @param applyUserRestriction the applyUserRestriction to set
     */
    public void setApplyUserRestrictions(boolean applyUserRestriction) {
        this.applyUserRestrictions = applyUserRestriction;
        if (applyUserRestriction)
            mountUserTree();
    }

    /**
     * @return the applyUserRestriction
     */
    public boolean isApplyUserRestrictions() {
        return applyUserRestrictions;
    }


    /**
     * Check if an administrative level can be changed or is fixed according to user restrictions
     * @param level from 1 to 5
     * @return true if administrative unit at the level is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevelReadOnly(int level) {
        int userLevel = getUserLevel();
        return userLevel >= level;
    }


    /**
     * Check if the administrative unit at level 1 can be changed
     * @return true if administrative unit at level 1 is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevel1ReadOnly() {
        return isLevelReadOnly(1);
    }


    /**
     * Check if the administrative unit at level 2 can be changed
     * @return true if administrative unit at level 2 is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevel2ReadOnly() {
        return isLevelReadOnly(2);
    }


    /**
     * Check if the administrative unit at level 3 can be changed
     * @return true if administrative unit at level 3 is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevel3ReadOnly() {
        return isLevelReadOnly(3);
    }


    /**
     * Check if the administrative unit at level 4 can be changed
     * @return true if administrative unit at level 4 is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevel4ReadOnly() {
        return isLevelReadOnly(4);
    }


    /**
     * Check if the administrative unit at level 5 can be changed
     * @return true if administrative unit at level 5 is fixed and cannot be changed, otherwise returns false
     */
    public boolean isLevel5ReadOnly() {
        return isLevelReadOnly(5);
    }


    /**
     * Check if the administrative unit in the 1st level is required for the patient address
     * @return true if is required
     */
    public boolean isLevel1Required() {
        return ((patientAddrRequiredLevels != null) && (patientAddrRequiredLevels >= 1));
    }


    /**
     * Check if the administrative unit in the 2nd level is required for the patient address
     * @return true if is required
     */
    public boolean isLevel2Required() {
        return ((patientAddrRequiredLevels != null) && (patientAddrRequiredLevels >= 2));
    }


    /**
     * Check if the administrative unit in the 3rd level is required for the patient address
     * @return true if is required
     */
    public boolean isLevel3Required() {
        return ((patientAddrRequiredLevels != null) && (patientAddrRequiredLevels >= 3));
    }


    /**
     * Check if the administrative unit in the 4th level is required for the patient address
     * @return true if is required
     */
    public boolean isLevel4Required() {
        return ((patientAddrRequiredLevels != null) && (patientAddrRequiredLevels >= 4));
    }


    /**
     * Check if the administrative unit in the 5th level is required for the patient address
     * @return true if is required
     */
    public boolean isLevel5Required() {
        return ((patientAddrRequiredLevels != null) && (patientAddrRequiredLevels >= 5));
    }

    /**
     * Include a listener to receive notification about selection changing
     * @param listener
     */
    public void addListener(AdminUnitChangeListener listener) {
        if (listeners == null) {
            listeners = new ArrayList<AdminUnitChangeListener>();
        }
        listeners.add(listener);
    }


    /**
     * Remove a notification changing listener
     * @param listener
     */
    public void remListener(AdminUnitChangeListener listener) {
        if (listeners == null) {
            return;
        }
        listeners.remove(listener);
    }


    /**
     * Notify about selection changing
     */
    protected void notifyChange() {
        if (listeners == null) {
            return;
        }

        for (AdminUnitChangeListener lst: listeners) {
            lst.notifyAdminUnitChange(this);
        }
    }


    /**
     * Return the id of the selected administrative unit
     * @return id in {@link Integer} format
     */
    public Integer getSelectedUnitId() {
        AdministrativeUnit adm = getSelectedUnit();
        if (adm == null) {
            return null;
        }
        else return adm.getId();
    }


    /**
     * Set the id of the selected administrative unit
     * @param id
     */
    public void setSelectedUnitId(Integer id) {
        //AK 20131111 set the admin unit id on the session level
        //SessionData.instance().setValue("uaid", id);
        if (id == null)
            setSelectedUnit(null);
        else {
            EntityManager em = (EntityManager)Component.getInstance("entityManager", true);
            setSelectedUnit(em.find(AdministrativeUnit.class, id));
        }
    }


    /**
     * Select units based on user restrictions
     */
    protected void selectUserRestrictions() {
        if (!applyUserRestrictions)
            return;

        UserWorkspace userWorkspace = (UserWorkspace)Component.getInstance("userWorkspace");

        if (userWorkspace == null)
            return;

        UserView view = userWorkspace.getView();
        if (view == UserView.COUNTRY)
            return;

        if (view == UserView.ADMINUNIT)
            setSelectedUnitId(userWorkspace.getAdminUnit() != null? userWorkspace.getAdminUnit().getId(): null);
        else setSelectedUnitId(userWorkspace.getTbunit().getAdminUnit().getId());
    }



    public String getSelectedUnitCodeLike() {
        AdministrativeUnit adm = getSelectedUnit();
        if (adm == null)
            return null;
        else return adm.getCode() + "%";
    }

    /**
     * @return the patientAddrRequiredLevels
     */
    public Integer getPatientAddrRequiredLevels() {
        return patientAddrRequiredLevels;
    }
}
