/*
 * DefnFileParser.java
 *
 * This parses the crop or operation DEFN config xml files. 
 *
 * Jim Frankenberger
 * USDA-ARS, West Lafayette IN
 * jrf@purdue.edu
 *
 * Created on July 21, 2004, 1:54 PM
 */
package ex1;

import de.schlichtherle.truezip.file.TFile;
import java.io.IOException;
import java.util.*;
import javax.swing.*;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;

/**
 *
 * This is the parser for the DEFN config files. This holds all the parameter meta-information.
 */
public class DefnFileParser extends DefaultHandler {

    // The following are used to hold temporary variables while parsing, these
    // values are later stored in permanent structures.
    private boolean grabName;
    private boolean grabType;
    private boolean grabUnit;
    private boolean grabId;
    private boolean grabCode;
    private boolean grabActionName;
    private boolean grabGroupId;
    private boolean grabGroupCode;

    private boolean inGroupIdSection;

    private boolean abs_min;
    private boolean abs_max;
    private boolean rec_min;
    private boolean rec_max;
    
    private boolean decomp;

    private String v_abs_min;
    private String v_abs_max;
    private String v_rec_min;
    private String v_rec_max;
    private Boolean v_abs_min_include;
    private Boolean v_abs_max_include;
    private Boolean v_rec_min_include;
    private Boolean v_rec_max_include;
    
    private String parmName;
    private String parmType;
    private String parmUnit;

    private String aName;
    private String aIdStr;
    private String groupIdStr;
    private char groupCode[];
    private int groupId[];
    private int groups;
    private char aCode;
    private int aId;
    private boolean hasUnits;
    private boolean inIdentitySection;
    /**
     * all parameters, key is name, value is ParamDef
     */
    private HashMap<String, ParamDef> map;
    /**
     * array of ParamDef ordered by column, for actions
     * a new one is allocated for each action, for crops there is only one
     */
    private ArrayList<ParamDef> childParms;
    /**
     * whether parsing a crop or operations defn file
     */
    private boolean isCropXML;
    /**
     * array of OpAction instances.
     */
    private ArrayList<OpAction> actionNames;

    /**
     * Creates a new instance of DefnFileParser
     *
     * @param defnFile the XML config file to load
     * @param isCrop true if this is for CROP data parser.
     */
    public DefnFileParser(String defnFile, boolean isCrop) {

        SAXParserFactory factory;
        SAXParser saxParser;
        grabName = grabUnit = grabType = false;
        grabGroupId = grabGroupCode = false;
        abs_min = abs_max = rec_min = rec_max = decomp = false;
        v_abs_min = v_abs_max = v_rec_min = v_rec_max = "";
        childParms = new ArrayList<ParamDef>();
        isCropXML = isCrop;
        inGroupIdSection = inIdentitySection = false;
        hasUnits = false;
        groupCode = new char[10];
        groupId = new int[10];
        for (int i = 0; i < 10; i++) {
            groupId[i] = -1;
            groupCode[i] = '?';
        }
        groups = 0;
        if (!isCrop) {
            actionNames = new ArrayList<OpAction>();
        }
        TFile wf = new TFile(defnFile);
        map = new HashMap<String, ParamDef>();

        if (wf.exists() == false) {
            JOptionPane.showMessageDialog(null, "Error: File not found",
                    wf.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE);

        }
        factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setXIncludeAware(true);            
            
        try {
            // Parse the input
            saxParser = factory.newSAXParser();
            saxParser.parse(wf, this);

        } catch (IOException | ParserConfigurationException | SAXException t) {
            t.printStackTrace();
        }
    }

    /**
     * Get the number of parameters defined in the DEFN file
     *
     * @return the number of parameters
     */
    public int getParmCount() {
        return map.size();
    }

    /*
     * Gets all the parameters as a collection.
     *
     * @return the collection of 
     */
    /**
     *
     * @return
     */
    public Collection<ParamDef> getAllParms() {
        return map.values();
    }

    /**
     * This gets the <prompt> field for a parameter declaration and appeands any units information
     * if it exists. These strings form the column headers in the tables the users sees. This
     * version is for crop data only.
     *
     * @param col - column (index of parameter) to get prompt for
     * @param altUnits - true if English version, false for metric
     * @return the prompt attached to this column with units appended
     */
    public String getParmPrompt(int col, boolean altUnits) {
        // handle two special columns outside parm structure
        if (col == 0) {
            return "Num";
        }
        if (col == 1) {
            return "Name";
        }
        if (col == 2) {
            return "Directory";
        }
        if (!isCropXML) {
            return "???";
        }
        col = col - 3;

        if (col < map.size()) {
            ParamDef p = childParms.get(col);
            if (p != null) {
                if ((altUnits == false) || (p.altUnits == null)) {
                    if (p.units != null) {
                        return p.prompt + "\n(" + p.units + ")";
                    } else {
                        return p.prompt;  // use default units
                    }
                } else if ((altUnits == true) && (p.altUnits != null)) {
                    return p.prompt + "\n(" + p.altUnits + ")";  // have alt units and want to use them
                } else {
                    return p.name;
                }
            } else {
                return "???";
            }
        } else {
            return "???";
        }
    }

    /**
     * Part of SAX XML processing... Called automatically by SAX parser
     *
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void startDocument()
            throws SAXException {
    }

    /**
     * Part of SAX processing... Called automatically by SAX parser
     *
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void endDocument()
            throws SAXException {
    }

    /**
     *
     * Part of SAX processing that gets called at the when an opening xml tag is encountered. We
     * will filter out the ones we are interested and set the flags for what the character function
     * should grab and where it will be stored.
     *
     * @param namespaceURI
     * @param attrs
     * @param lName local name
     * @param qName qualified name
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void startElement(String namespaceURI, String lName,
            String qName, Attributes attrs) throws SAXException {
        grabName = grabType = grabUnit = grabId = grabCode = grabActionName = false;

        String eName = lName; // element name
        if ("".equals(eName)) {
            eName = qName;
        }
        if (eName.equals("comment")) {
            return;
        }

        if (isCropXML == false) {
            if (eName.equals("id") && (inIdentitySection)) {
                if (inGroupIdSection == false) {
                    grabId = true;
                    aIdStr = "";
                } else {
                    grabGroupId = true;
                    groupIdStr = "";
                }
                return;
            } else if (eName.equals("code") && (inIdentitySection)) {  // these are single char fields
                if (inGroupIdSection == false) {
                    grabCode = true;
                } else {
                    grabGroupCode = true;
                }
                return;
            } else if (eName.equals("actionname")) {
                grabActionName = true;
                aName = "";
                return;
            } else if (eName.equals("groupid")) {
                //groupId = -1;
                //groupCode = '?';
                inGroupIdSection = true;
                return;
            } else if (eName.equals("action")) {
                //groupId = -1;
                //groupCode = '?';
                childParms = new ArrayList<ParamDef>();
            } else if (eName.equals("identity")) {
                inIdentitySection = true;
            } else if (eName.equals("comment")) {
            }
        }
        switch (eName) {
            case "paramname":
                grabName = true;
                parmName = "";
                break;
            case "paramtype":
                parmType = "";
                grabType = true;
                break;
            case "paramunit":
                grabUnit = true;
                hasUnits = true;
                parmUnit = "";
                break;
            case "abs_min":
                v_abs_min = "";
                if(attrs.getLength() == 0) v_abs_min_include = null;
                for(int index = 0; index < attrs.getLength(); index ++)
                {
                    if("exclude".equalsIgnoreCase(attrs.getQName(index)))
                    {
                        v_abs_min_include = !attrs.getValue(index).equals("true");
                        if(attrs.getValue(index).equals("")) v_abs_min_include = null;
                    }
                }
                abs_min = true;
                break;
            case "abs_max":
                v_abs_max = "";
                if(attrs.getLength() == 0) v_abs_max_include = null;
                for(int index = 0; index < attrs.getLength(); index ++)
                {
                    if("exclude".equalsIgnoreCase(attrs.getQName(index)))
                    {
                        v_abs_max_include = !attrs.getValue(index).equals("true");
                        if(attrs.getValue(index).equals("")) v_abs_max_include = null;
                    }
                }
                abs_max = true;
                break;
            case "rec_min":
                v_rec_min = "";
                if(attrs.getLength() == 0) v_rec_min_include = null;
                for(int index = 0; index < attrs.getLength(); index ++)
                {
                    if("exclude".equalsIgnoreCase(attrs.getQName(index)))
                    {
                        v_rec_min_include = !attrs.getValue(index).equals("true");
                        if(attrs.getValue(index).equals("")) v_rec_min_include = null;
                    }
                }
                rec_min = true;
                break;
            case "rec_max":
                v_rec_max = "";
                if(attrs.getLength() == 0) v_rec_max_include = null;
                for(int index = 0; index < attrs.getLength(); index ++)
                {
                    if("exclude".equalsIgnoreCase(attrs.getQName(index)))
                    {
                        v_rec_max_include = !attrs.getValue(index).equals("true");
                        if(attrs.getValue(index).equals("")) v_rec_max_include = null;
                    }
                }
                rec_max = true;
                break;
            case "decomp":
                decomp = true;
                break;
        }
    }

    /**
     * Part of SAX processing that gets called when an ending xml tag is encountered.
     *
     * @param namespaceURI
     * @param qName qualified name
     * @param sName simple name
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void endElement(String namespaceURI, String sName, String qName) throws SAXException {
        String eName = sName; // element name
        if ("".equals(eName)) {
            eName = qName;
        }
        if (eName.equals("comment")) {
            return;
        }
        //
        // End of a parameter definition, add the info to the list. Operation
        // parameters have the process ID appended to the name so that if different
        // processes have the same parameter name everything will be unique.
        //
        switch (eName) {
            case "abs_min":
                v_abs_min = v_abs_min.trim();
                abs_min = false;
                break;
            case "abs_max":
                v_abs_max = v_abs_max.trim();
                abs_max = false;
                break;
            case "rec_min":
                v_rec_min = v_rec_min.trim();
                rec_min = false;
                break;
            case "rec_max":
                v_rec_max = v_rec_max.trim();
                rec_max = false;
                break;
            case "decomp": break;
            case "paramdefn":
                ParamDef o = new ParamDef();
                o.actualName = parmName;
                o.il = new InputLimits(v_abs_min, v_abs_max, v_rec_min, v_rec_max,
                    v_abs_min_include, v_abs_max_include, v_rec_min_include, v_rec_max_include);
                v_abs_min = "";
                v_abs_max = "";
                v_rec_min = "";
                v_rec_max = "";
                o.setDecomp(decomp);
                decomp = false;
                
                String parmName2 = parmName;
                if (!isCropXML) {
                    if (aIdStr != null) {
                        //chages by Isaac Haas
                        parmName2 = parmName2 + String.valueOf(aId);
                    }
                }
                o.name = parmName2;
                if (hasUnits) {
                    o.units = parmUnit;
                }
                o.setType(ParamDef.ColumnType.FLOAT);
                if (parmType != null) {
                    switch (parmType) {
                        case "float":
                            o.setType(ParamDef.ColumnType.FLOAT);
                            break;
                        case "int":
                            o.setType(ParamDef.ColumnType.INTEGER);
                            break;
                        default:
                            o.setType(ParamDef.ColumnType.STRING);
                            break;
                    }
                }
                o.choices = new ArrayList<ChoiceParm>();
                if (map.containsKey(parmName2)) {
                    String estr = "";
                    if (!isCropXML) {
                        estr = "Parameter already present: " + parmName + "in process/group " + aIdStr;
                    } else {
                        estr = "Parameter already present: " + parmName2;
                    }
                    ErrorSink.add(estr);
                    //System.out.println(estr);

                }
                map.put(parmName2, o);
                childParms.add(o);
                //           System.out.println("ChildPams:"+o.name+"|Type:"+o.getType()+"!Min"+o.rec_min);
                hasUnits = false;
                break;
            case "actionname":
                aName = aName.trim();
                grabActionName = false;
                break;
            case "groupid":
                inGroupIdSection = false;
                break;
            case "identity":
                if (inGroupIdSection) {
                    groups++;
                }
                inIdentitySection = false;
                break;
            case "action":
                // add this info to the action structure
                OpAction a = new OpAction(aName, aCode, aId);
                if (groups > 0) {
                    for (int i = 0; i < groups; i++) {
                        if (groupId[i] != -1) {
                            a.groupId[i] = groupId[i];
                        }
                        if (groupCode[i] != '?') {
                            a.groupCode[i] = groupCode[i];
                        }
                    }
                    a.groups = groups;
                    groups = 0;
                }
                a.addParmList(childParms);
                actionNames.add(a);
                break;
            case "paramname":
                parmName = parmName.trim();
                grabName = false;
                break;
            case "paramtype":
                parmType = parmType.trim();
                grabType = false;
                break;
            case "paramunit":
                parmUnit = parmUnit.trim();
                grabUnit = false;
                break;
            case "id":
                if (inGroupIdSection == false) {
                    grabId = false;
                    aIdStr = aIdStr.trim();
                    aId = Integer.parseInt(aIdStr);
                } else {
                    grabGroupId = false;
                    groupIdStr = groupIdStr.trim();
                    groupId[groups] = Integer.parseInt(groupIdStr);
                }
                break;
        }

    }

    /**
     * Part of SAX processing that gets called to get the contents inside an xml tag.
     *
     * @param buf
     * @param len
     * @param offset
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void characters(char buf[], int offset, int len) throws SAXException {
        String s = new String(buf, offset, len);
        if (grabName) {
            parmName = parmName.concat(s);
        } else if (grabUnit) {
            parmUnit = parmUnit.concat(s);
        } else if (grabType) {
            parmType = parmType.concat(s);
        } else if (grabActionName) {
            aName = aName.concat(s);
        } else if (grabCode) {
            aCode = s.charAt(0);
        } else if (grabId) {
            aIdStr = aIdStr.concat(s);
        } else if (grabGroupId) {
            groupIdStr = groupIdStr.concat(s);
        } else if (grabGroupCode) {
            groupCode[groups] = s.charAt(0);
        } else if (abs_min) {
            v_abs_min = v_abs_min.concat(s);
        } else if (abs_max) {
            v_abs_max = v_abs_max.concat(s);
        } else if (rec_min) {
            v_rec_min = v_rec_min.concat(s);
        } else if (rec_max) {
            v_rec_max = v_rec_max.concat(s);
        }
        grabCode = grabGroupCode = false;   // these are single char fields, will not be split up

    }

    /**
     * Given a parameter name finds the structure holding its information.
     *
     * @param name name of the parameter to find
     * @return structure holding all info about this parameter
     */
    public ParamDef getParamDef(String name) {
        ParamDef p = map.get(name);
        return p;
    }

    /**
     * Sets the user prompt that should be displayed with this parameter.
     *
     * @param name name of the parameter
     * @param prompt prompt string to attach to it
     */
    public void setPromptName(String name, String prompt) {
        ParamDef p = map.get(name);
        if (p != null) {
            // found it  
            p.prompt = prompt;
        }
    }

    /**
     * Set string to display when alternate units are dispalyed.
     *
     * @param name name of the parameter
     * @param altUnit string to attach to alternate units
     */
    public void setAltUnit(String name, String altUnit) {
        ParamDef p = map.get(name);
        if (p != null) {
            // found it  
            p.altUnits = altUnit;
        }
    }

    /**
     * Sets the factor that is used to go from regular to alternate units.
     *
     * @param name parameter name
     * @param factor value to multiply base value by to get alt value (generally mettric to english)
     */
    public void setFactor(String name, float factor) {
        ParamDef p = map.get(name);
        if (p != null) {
            // found it  
            p.factor = factor;
        }
    }

    /**
     * As part of the alternate (metric to english) conversion this may be added after the factor is
     * applied.
     *
     * @param name name of the parameter
     * @param adder value to add at the end
     */
    public void setAddEnd(String name, float adder) {
        ParamDef p = map.get(name);
        if (p != null) {
            // found it  
            p.addEnd = adder;
        }
    }

    /**	
     * Add a choice to this parameter.
     *
     * @param name name of the parameter
     * @param val value of the particular choice
     * @param choice name of string to display with this val in a choice list
     */
    public void setChoice(String name, int val, String choice) {
        ParamDef p = map.get(name);
        if (p != null) {
            // found it  
            p.setType(ParamDef.ColumnType.CHOICE);
            ChoiceParm choicep = new ChoiceParm(val, choice);
            p.choices.add(choicep);
        }
    }

    /**
     * This gets the number of choices for a parameter that is a choice list
     *
     * @param col column in crop table
     * @return number of choices for the dropdown
     */
    public int getChoiceCount(int col) {
        col = col - 3;
        if (col < map.size()) {
            ParamDef p = childParms.get(col);
            if (p != null) {
                return p.choices.size();
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }
    
    public InputLimits getLimits(String name) {
        ParamDef p = map.get(name);
        return p.il;
    }
    
    public InputLimits getLimits(String paramName, String actionName)
    {
        for(OpAction act : actionNames)
        {
            if(actionName.equals(act.name))
            {
                for(ParamDef parm : act.childParms)
                {
                    if(paramName.equals(parm.name))
                    {
                        return parm.il;
                    }
                }
                return null;
            }
        }
        return null;
    }
    
    public InputLimits getLimits(int iopen, int col) {
        OpAction op = actionNames.get(iopen);
        if (op != null) {
            ParamDef p = op.getColumn(col);
            if (p != null) {
                return p.il;
            }
        }
        return null;
    }

    /**
     * This function returns one choice string from the list of choices for a parameter.
     *
     * @param col column in crop data
     * @param ch index of choice string to get
     * @return the choice value at this specific index
     */
    public String getChoice(int col, int ch) {
        col = col - 3;
        if (col < map.size()) {
            ParamDef p = childParms.get(col);
            if (p != null) {
                ChoiceParm pc = p.choices.get(ch);
                return pc.choice;
            } else {
                return "?";
            }
        } else {
            return "?";
        }
    }

    /**
     * Return the type of this parameter for crop data
     * todo: refactor to use an enumeration
     *
     * @param col column in crop data
     * @return 0,1,2,3 for int, floa, string, choice list
     */
    public ParamDef.ColumnType getType(int col) {
        if (col <= 2) {
            return ParamDef.ColumnType.FLOAT;  // to account for the number and filename columns
        }
        col -= 3;

        if (col < map.size()) {
            ParamDef p = childParms.get(col);
            if (p != null) {
                return p.getType();
            } else {
                return ParamDef.ColumnType.FLOAT;
            }
        } else {
            return ParamDef.ColumnType.FLOAT;
        }
    }

    /**
     * Return the type of this parameter for crop data
     * todo: refactor to use an enumeration
     *
     * @param col column in crop data
     * @param parmName name of the parameter
     * @return 0,1,2,3 for int, floa, string, choice list
     */
    public ParamDef.ColumnType getOprnType(int col, String parmName) {
        if (col <= 2) {
            return ParamDef.ColumnType.FLOAT;  // to account for the number and filename columns
        }
        col -= 3;

        if (col < map.size()) {
            for(OpAction act : actionNames)
            {
                ArrayList<ParamDef> check = act.childParms;
                for(ParamDef parm : check)
                {
                    if(parm.name.equals(parmName))
                    {
                        return parm.getType();
                    }
                }
            }
            return ParamDef.ColumnType.FLOAT;
        } else {
            return ParamDef.ColumnType.FLOAT;
        }
    }
    
    /**
     * Return the Class of this parameter for crop data (either a string or combobox)
     *
     * @param col column in crop data
     * @return either String or JComboBox depending on column type
     */
    public Class<?> getClass(int col) {
        if (getType(col) != ParamDef.ColumnType.STRING &&
                getType(col) != ParamDef.ColumnType.CHOICE) {
            String s = "";
            return s.getClass();
        } else {
            JComboBox<?> b = new JComboBox<>();
            return b.getClass();
        }
    }

    /**
     * From a choice string find the value that is linked to it.
     *
     * @param p parameter definition
     * @param ch choice string to search for
     * @return value that goes with selected choice string
     */
    public int findChoiceVal(ParamDef p, String ch) {
        for (ChoiceParm pc : p.choices) {
            if (pc.choice.equals(ch)) {
                return pc.val;
            }
        }
        return -2;
    }

    /**
     * Get the number of unique process names.
     *
     * @return the number of weps process and group types possible.
     */
    public int getActionNameCount() {
        return actionNames.size();
    }

    /**
     * Get the numeric part of the process at this location
     *
     * @param ind index in action/process name list
     * @return the numeric part of the process name or -1 on error
     */
    public int getOpParmCode(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.id;
        } else {
            return -1;
        }
    }

    /**
     * Get the character part of the process at this location.
     *
     * @param ind index in action/process name list
     * @return the single charcter O,G,P part of the process name
     */
    public char getOpParmCodeType(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.code;
        } else {
            return 'X';
        }
    }

    /**
     * Get number of parameters for a process.
     *
     * @param ind index in action/process name list
     * @return number of operation parameters under this process
     */
    public int getOpParmCount(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.childParms.size();
        } else {
            return 0;
        }
    }

    /**
     * Get the type of a specific parameter in a process.
     *
     * @param ind index in action/process name list
     * @param pind index of parameter within the process
     * @return the type of the parameter
     */
    public ParamDef.ColumnType getOpParmType(int ind, int pind) {
        OpAction op = actionNames.get(ind);
        //     System.out.println ("Ind and pind:"+ind+ " "+pind);
        if (op != null) {
            ParamDef p = op.getColumn(pind);
            /*
             if (pind < op.childParms.size())
             {
             p = op.childParms.get(pind);
             }
             //    ParamDef p = op.getColumn(pind);
   
             //  ParamDef p = map.get(pind);
             */
            if (p != null) {
                return p.getType();
            }
        }
        return ParamDef.ColumnType.CHOICE;
    }

    /**
     * Get the number of choices that a dropdown list in process contains
     *
     * @param ind index in action/process name list
     * @param pind index of parameter within the process
     * @return number of choices for the list.
     */
    public int getOpChoiceCount(int ind, int pind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            ParamDef p = op.childParms.get(pind);
            if (p != null) {
                return p.choices.size();
            }
        }
        return 0;
    }

    /**
     * Get a specific dropdown choice within a process.
     *
     * @param ind index in action/process name list
     * @param pind index of parameter within the process
     * @param ch choice list element to get
     * @return the choice list string that the specified location
     */
    public String getOpChoice(int ind, int pind, int ch) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            ParamDef p = op.childParms.get(pind);
            if (p != null) {
                ChoiceParm pc = p.choices.get(ch);
                return pc.choice;
            } else {
                return "?";
            }
        }
        return "?";
    }

    /**
     * Get the prompt(header) for an operation parameter.
     *
     * @param ind index in action/process name list
     * @param pind index of parameter within the process
     * @param altUnits true if alternate(english) units are to be used
     * @return the header for the operation parameter with units appended
     */
    public String getOpParmPrompt(int ind, int pind, boolean altUnits) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            ParamDef p = op.childParms.get(pind);
            if (p != null) {
                if ((altUnits == false) || (p.altUnits == null)) {
                    if (p.units != null) {
                        return p.prompt + "\n(" + p.units + ")";
                    } else {
                        return p.prompt;
                    }
                } else if ((altUnits == true) && (p.altUnits != null)) {
                    return p.prompt + "\n(" + p.altUnits + ")";
                } else {
                    return p.name;
                }
            } else {
                return "???";
            }
        } else {
            return "???";
        }
    }

    /**
     * Get the name of a process at a specific location.
     *
     * @param ind index in action/process name list
     * @return the full name of the process
     */
    public String getActionName(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.name;
        } else {
            return "";
        }
    }

    /**
     * Get the process structure at the specified index.
     *
     * @param ind index in action/process name list
     * @return structure containing process information
     */
    public OpAction getAction(int ind) {
        return actionNames.get(ind);
    }

    /**
     * Get the name of the process with the the id and code tacked on the front.
     *
     * @param ind index in action/process name list
     * @return full name such as 'P11 - Break Crust'
     */
    public String getFullActionName(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.getFullName();
        } else {
            return "";
        }
    }

    /**
     * Get the name of the process with the the id and code tacked on the front.
     *
     * @param code single character code to match
     * @param id numeric id to match.
     * @return full name such as 'P11 - Break Crust'
     */
    public String getFullActionName(String code, String id) {
        OpAction a = findOpAction(code, id);
        if (a != null) {
            return a.getFullName();
        } else {
            return "???";
        }
    }

    /**
     * Get the process structure that matches the specified code and String numeric identifier.
     *
     * @param code single charcter code to match
     * @param id numeric (as String) id to match
     * @return process structure with information
     */
    public OpAction findOpAction(String code, String id) {
        OpAction a;
        int c = Integer.parseInt(id);
        for (OpAction actionName : actionNames) {
            a = actionName;
            if ((a.id == c) && (code.charAt(0) == a.code)) {
                return a;
            }
        }
        return null;
    }

    /**
     * Get the process structure that matches the specified code and numeric identifier.
     *
     * @param code single charcter code to match
     * @param id numeric id to match
     * @return process structure with information
     */
    public OpAction findOpAction(String code, int id) {
        OpAction a;
        int c = id;
        for (OpAction actionName : actionNames) {
            a = actionName;
            if (a.id == c) {
                if (c == 1) {
                    if (code.charAt(0) == a.code) {
                        return a;
                    }
                } else {
                    return a;
                }
            }
        }
        return null;
    }

    /**
     * Get the single character code O,G,P for the process at the specified index.
     *
     * @param ind index in action/process name list
     * @return the code as a string
     */
    public String getActionCode(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            String p = String.valueOf(op.code);
            return p;
        } else {
            return "";
        }
    }

    //get the operation column name
    public String getOpParmName(int ind, int pind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            ParamDef p = op.childParms.get(pind);
            //         ParamDef p = op.getColumn(pind); 
            if (p != null) {
                return p.name;
            } else {
                return "";
            }
        } else {
            return "";
        }
    }

    /**
     * Get the numeric part of the process at the specified index
     *
     * @param ind index in action/process name list
     * @return numeric part of process name
     */
    public int getActionId(int ind) {
        OpAction op = actionNames.get(ind);
        if (op != null) {
            return op.id;
        } else {
            return -1;
        }
    }

    /**
     * Get the parameter meta information for a specific column in a crop table
     *
     * @param col column information to get
     * @return the ParamDef structure for this column.
     */
    public ParamDef getColumn(int col) {
        if (col < childParms.size()) {
            return childParms.get(col);
        } else {
            return null;
        }
    }
    
    /**
     * Get the parameter meta information for a specific column in a specific
     * Operation table
     *
     * @param col column information to get
     * @return the ParamDef structure for this column.
     */
    public ParamDef getColumn(int col, String actionName)
    {
        for(OpAction act : actionNames)
        {
            if(actionName.equals(act.name))
            {
                if (col < act.childParms.size()) {
                    return act.childParms.get(col);
                } else {
                    return null;
                }
            }
        }
        return null;
    }
    
    public int getActionTab(String name)
    {
        for(int index = 0; index < actionNames.size(); index ++)
        {
            if(actionNames.get(index).name.equals(name)) return index;
        }
        return 0;
    }
    
    
};
