package usda.weru.weps;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.JTextField;
import usda.weru.util.ConfigData;

/**
 * Contains the level of output desired for each of the six submodels
 * @author maxerdwien
 */
public class SubmodelOutput {

    /**
     * all of the submodels that can be passed values to specify level of output
     */
    public enum submodel {

        Hydrology, Soil, Management, Crop, Decomposition, Erosion
    };

    private final Map<submodel, Integer> maxValues;

    private Map<submodel, Integer> defaults;

    /**
     * contains the six submodel output values
     */
    private Map<submodel, Integer> submodelOutput;

    private Map<submodel, JTextField> textFieldHandles;

    /**
     * the names of the different .out files that WEPS can generate
     */
    public enum outFile {

        crop, dabove, dbelow, decomp, hlayers, hydro, plot, season, shoot, soillay
    };

    /**
     * a mapping of which submodel output flags must be turned on to get certain output files
     */
    private final Map<outFile, Map<submodel, Integer>> outFileMapping
            = new EnumMap<outFile, Map<submodel, Integer>>(outFile.class);

    private final Map<outFile, Boolean> checked = new EnumMap<outFile, Boolean>(outFile.class);

    private final ConfigData cd;

    /**
     *
     * @param config where to get the default values
     */
    public SubmodelOutput(ConfigData config) {
        maxValues = setMap(7, 1, 1, 1, 3, 3);
        initOutFileMapping();
        initChecked();
        cd = config;
        initDefaults();
        setSubmodelOutput(defaults);
    }

    private void initOutFileMapping() {
        outFileMapping.put(outFile.crop, setMap(0, 0, 0, 1, 0, 0));
        outFileMapping.put(outFile.shoot, setMap(0, 0, 0, 1, 0, 0));

        outFileMapping.put(outFile.hydro, setMap(1, 0, 0, 0, 0, 0));
        outFileMapping.put(outFile.hlayers, setMap(1, 0, 0, 0, 0, 0));

        outFileMapping.put(outFile.decomp, setMap(0, 0, 0, 0, 1, 0));
        outFileMapping.put(outFile.dabove, setMap(0, 0, 0, 0, 1, 0));
        outFileMapping.put(outFile.dbelow, setMap(0, 0, 0, 0, 2, 0));

        outFileMapping.put(outFile.soillay, setMap(0, 1, 0, 0, 0, 0));

        outFileMapping.put(outFile.season, setMap(0, 0, 0, 0, 0, 0));

        outFileMapping.put(outFile.plot, setMap(0, 0, 0, 0, 0, 0));
    }

    private void initChecked() {
        for (outFile f : outFile.values()) {
            checked.put(f, false);
        }
    }

    /**
     *
     * @return true iff 'defaults' has been changed
     */
    private Boolean initDefaults() {
        Map<submodel, Integer> temp = setMap(
                Integer.parseInt(cd.getData(ConfigData.OPHydroDet)),
                Integer.parseInt(cd.getData(ConfigData.OPSoilDet)),
                Integer.parseInt(cd.getData(ConfigData.OPManageDet)),
                Integer.parseInt(cd.getData(ConfigData.OPCropDet)),
                Integer.parseInt(cd.getData(ConfigData.OPDecompDet)),
                Integer.parseInt(cd.getData(ConfigData.OPErosionDet)));

        // clean up all the values
        for (submodel s : temp.keySet()) {
            Integer newValue = cleanText(s, temp.get(s));
            if (!Objects.equals(newValue, temp.get(s))) {
                System.out.println("Config file contains invalid submodel output values. "
                        + s.toString() + ": " + temp.get(s));
                temp.put(s, newValue);
            }
        }

        if (temp.equals(defaults)) {
            return false;
        } else {
            defaults = temp;
            return true;
        }
    }

    private void setSubmodelOutput(Map<submodel, Integer> m) {
/**
                 * Note:  Assertions are not enabled.  These will be useless items
                 * unless assertions are enabled.  Thus, they will be commented out unless
                 * the user wishes to enable specific assertions (feed the virtual machine 
                 * the -ea argument).
                 */
//        assert (m.values().size() == 6);
        submodelOutput = new EnumMap<submodel, Integer>(m);
    }

    private EnumMap<submodel, Integer> setMap(int... array) {
        EnumMap<submodel, Integer> m = new EnumMap<submodel, Integer>(submodel.class);
/**
                 * Note:  Assertions are not enabled.  These will be useless items
                 * unless assertions are enabled.  Thus, they will be commented out unless
                 * the user wishes to enable specific assertions (feed the virtual machine 
                 * the -ea argument).
                 */
//        assert (array.length == 6);
        m.put(submodel.Hydrology, array[0]);
        m.put(submodel.Soil, array[1]);
        m.put(submodel.Management, array[2]);
        m.put(submodel.Crop, array[3]);
        m.put(submodel.Decomposition, array[4]);
        m.put(submodel.Erosion, array[5]);
        return m;
    }

    /**
     * 
     * @return submodel output value, reformatted to a string (eg "1 2 1 0 0 3")
     */
    public String getAllSubmodelOutput() {
        String returnValue = "";
        returnValue += submodelOutput.get(submodel.Hydrology) + " ";
        returnValue += submodelOutput.get(submodel.Soil) + " ";
        returnValue += submodelOutput.get(submodel.Management) + " ";
        returnValue += submodelOutput.get(submodel.Crop) + " ";
        returnValue += submodelOutput.get(submodel.Decomposition) + " ";
        returnValue += submodelOutput.get(submodel.Erosion);
        return returnValue;
    }

    /**
     *
     * @param s
     * @return the value of the specified submodel
     */
    public String getSubmodelOutput(submodel s) {
        return submodelOutput.get(s).toString();
    }

    /**
     * gets a value from the text fields associated with the given submodel,
     * then cleans it and sets the internal representation of the data
     * @param s 
     */
    public void setOutput(submodel s) {
        Integer newValue = cleanText(s, Integer.parseInt(textFieldHandles.get(s).getText()));
        // set the text field to the new value, which will be different from its
        // current value if the input was badly formatted
        submodelOutput.put(s, newValue);
    }

    /**
     * flushes out all of the handles to text fields
     */
    public void clearTextFields() {
        if (textFieldHandles == null) {
            textFieldHandles = new EnumMap<submodel, JTextField>(submodel.class);
        }
        textFieldHandles.clear();
    }

    /**
     * remembers a JTextField handle associated with a submodel, so that we can update the
     * text fields later
     * @param s
     * @param jtf 
     */
    public void setTextField(submodel s, JTextField jtf) {
        textFieldHandles.put(s, jtf);
    }

    /**
     * sets the given outFile to the given boolean state, then reconstructs
     * the submodelOutput by starting with the defaults and adding only what
     * is needed to satisfy the checked outFiles.
     * @param f
     * @param b 
     */
    public void setChecked(outFile f, Boolean b) {
        checked.put(f, b);
        // reset to the default
        setSubmodelOutput(defaults);
        // loop through all checkboxes, rebuilding the submodel output
        for (outFile out : outFile.values()) {
            if (checked.get(out)) {
                enableOutputFile(out);
            }
        }
        // special case: the only way to get both dbelow and dabove as output
        // is to have decomposition set to three
        if (checked.get(outFile.dabove) && checked.get(outFile.dbelow)) {
            if (submodelOutput.get(submodel.Decomposition) < 3) {
                submodelOutput.put(submodel.Decomposition, 3);
            }
        }

        updateText();
    }

    /**
     *
     * @param f
     * @return whether the checkbox associated with the outFile is checked
     */
    public Boolean isChecked(outFile f) {
        return checked.get(f);
    }

    /**
     * update the submodel output to be in line with the minimum requirement to get
     * the desired output file
     * @param f 
     */
    private void enableOutputFile(outFile f) {
        for (submodel s : outFileMapping.get(f).keySet()) {
            if (outFileMapping.get(f).get(s) > submodelOutput.get(s)) {
                submodelOutput.put(s, outFileMapping.get(f).get(s));
            }
        }
    }

    /**
     * checks whether i is valid for the given submodel and
     * returns a corrected value
     * @param s
     * @return 
     */
    private Integer cleanText(submodel s, Integer i) {
        try {
            if (i < 0) {
                return 0;
            } else if (i > maxValues.get(s)) {
                return maxValues.get(s);
            }
            return i;
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    /**
     * pushes the values held in submodelOutputs to the visible text fields
     */
    private void updateText() {
        for (submodel s : submodel.values()) {
            textFieldHandles.get(s).setText(submodelOutput.get(s).toString());
        }
    }

    /**
     *
     * @return true iff the config data has changed
     */
    public Boolean refresh() {
        Boolean returnValue = false;
        if (initDefaults()) {
            setSubmodelOutput(defaults);
            initChecked();
            returnValue = true;
        }
        updateText();
        return returnValue;
    }
    
    public outFile getType(String name) {
        Map<String, outFile> map = new HashMap<>();
        map.put("crop", SubmodelOutput.outFile.crop);
        map.put("dabove", SubmodelOutput.outFile.dabove);
        map.put("dbelow", SubmodelOutput.outFile.dbelow);
        map.put("decomp", SubmodelOutput.outFile.decomp);
        map.put("hlayers", SubmodelOutput.outFile.hlayers);
        map.put("hydro", SubmodelOutput.outFile.hydro);
        map.put("plot", SubmodelOutput.outFile.plot);
        map.put("season", SubmodelOutput.outFile.season);
        map.put("shoot", SubmodelOutput.outFile.shoot);
        map.put("soillay", SubmodelOutput.outFile.soillay);
        
        return map.get(name);
    }

}
