/*
 * RunNameChooser.java
 *
 * Created on March 17, 2006, 1:24 PM
 *
 */
package usda.weru.weps;

import de.schlichtherle.truezip.file.TFile;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.*;
import usda.weru.util.*;
import usda.weru.weps.gui.*;

/**
 * RunNameChooser is a dialog box used for selecting a run name.  The dialog box
 * also allows the user to select the location for the run.  A set of checks is 
 * performed on the run name and location to make sure it meets the requirements
 * before the user has to click ok.
 * @author Joseph Levin
 */
public class RunNameChooser extends RunNameChooser_n implements DocumentListener, FocusListener,
        ActionListener, PropertyChangeListener {

    private static final long serialVersionUID = 1L;

    /**
     * File object which represents the new run.
     * Run Location + Run Name + RunNameSuffix
     */
    protected TFile c_runFile;

    /**
     * References the Ok button so the CheckForErrors() method can update the enabled
     * state of the button.
     */
    protected JButton c_okButton;

    /**
     * References the JOptionPane so the CheckForErrors() method can change the icon
     * to indicate the existence of an error.
     */
    protected JOptionPane c_jop;

    /**
     * References the component which should be given focus after CheckForErrors() is 
     * called.  This is to fix odd behavior in which the text fields lost focus due
     * to the error checking.
     */
    protected Component c_lastFocus;

    /**
     *
     */
    protected ConfigData c_cd;

    /**
     *
     */
    protected RunFileData c_rfd;

    /**
     *
     */
    protected String c_runsLocation;

    /**
     *
     */
    protected String c_defaultRunsLocation;

    /**
     *
     */
    protected String c_lastAttemptedRun;

    /**
     *
     */
    protected String c_lastRun;

    private final java.util.List<JCheckBox> enabledCheckboxes = new ArrayList<>();

    /**
     * Creates a new instance of RunNameChooser
     * @param rfd
     */
    public RunNameChooser(RunFileData rfd, boolean calMode) {
        super();
        c_cd = ConfigData.getDefault();
        c_cd.fireAll(this);

        c_rfd = rfd;
        c_rfd.fireAll(this);

        //Document listeners
        JTF_runname.getDocument().addDocumentListener(this);
        JTF_runlocation.getDocument().addDocumentListener(this);

        //Focus listeners
        JTF_runname.addFocusListener(this);
        JTF_runlocation.addFocusListener(this);

        //Action Listeners
        JTF_runname.addActionListener(this);
        JTF_runlocation.addActionListener(this);

        String runName = "new run";

        if (c_lastAttemptedRun != null && c_lastAttemptedRun.length() > 0) {
            runName = c_lastAttemptedRun;
        }

        String runLocation = c_runsLocation;
        if (runLocation == null || runLocation.trim().length() == 0) {
            runLocation = c_defaultRunsLocation;
        }

        //FIXME: this is the original code -- above is fixed to remove redundant null check but may be wrong
//        if(runLocation == null || runLocation.trim().length() == 0) runLocation = c_runsLocation;
//        if(runLocation == null || runLocation.trim().length() == 0) {
//            runLocation = c_defaultRunsLocation;
//        }
        if(calMode)
        {
            c_runFile = Util.incrementFileName(new TFile(runLocation, runName + RunFileData.RunSuffix),
                    "_calib", RunFileData.RunSuffix);
        }
        else
        {
            c_runFile = Util.incrementFileName(new TFile(runLocation, runName + RunFileData.RunSuffix),
                    null, RunFileData.RunSuffix);
        }
        

        JTF_runname.setText(Util.purgeExtensions(c_runFile.getName(), RunFileData.RunSuffix));
        JTF_runlocation.setText(runLocation);

        setSubmodelOutputVisiblity();

        finishPanel(jPanel1);

        c_rfd.so.clearTextFields();

        // give the SubmodelOutput handles to all of the textfields
        c_rfd.so.setTextField(SubmodelOutput.submodel.Hydrology, JTF_hydrology);
        c_rfd.so.setTextField(SubmodelOutput.submodel.Soil, JTF_soil);
        c_rfd.so.setTextField(SubmodelOutput.submodel.Management, JTF_management);
        c_rfd.so.setTextField(SubmodelOutput.submodel.Crop, JTF_crop);
        c_rfd.so.setTextField(SubmodelOutput.submodel.Decomposition, JTF_decomposition);
        c_rfd.so.setTextField(SubmodelOutput.submodel.Erosion, JTF_erosion);

        if (!c_rfd.so.refresh()) {
            for (JCheckBox jcb : enabledCheckboxes) {
                jcb.setSelected(c_rfd.so.isChecked(c_rfd.so.getType(jcb.getName())));
            }
        }

    }

    private void finishPanel(JPanel jp) {
        jp.setLayout(new GridLayout(6, 2));

        addBox("crop", "crop", SubmodelOutput.outFile.crop);

        addBox("dabove", "dabove", SubmodelOutput.outFile.dabove);

        addBox("dbelow", "dbelow", SubmodelOutput.outFile.dbelow);

        addBox("decomp", "decomp", SubmodelOutput.outFile.decomp);

        addBox("hlayers", "hlayers", SubmodelOutput.outFile.hlayers);

        addBox("hydro", "hydro", SubmodelOutput.outFile.hydro);

        addBox("plot", "plot", SubmodelOutput.outFile.plot);

        addBox("season", "season", SubmodelOutput.outFile.season);

        addBox("shoot", "shoot", SubmodelOutput.outFile.shoot);

        addBox("soillay", "soillay", SubmodelOutput.outFile.soillay);

        for (JCheckBox jcb : enabledCheckboxes) {
            jp.add(jcb);
        }

        jp.setVisible(true);
    }

    private void addBox(String name, String prettyName, final SubmodelOutput.outFile t) {
        final JCheckBox newBox = new JCheckBox(prettyName);

        newBox.setName(name);

        newBox.addActionListener(new java.awt.event.ActionListener() {
            @Override
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                checkboxEvent(t, newBox.isSelected());
            }
        });

        switch (c_cd.getData("CD-visibility " + name)) {
            case "Read Write":
                enabledCheckboxes.add(newBox);
                break;
            case "Read Only":
                newBox.setEnabled(false); // set to be greyed-out
                enabledCheckboxes.add(newBox); // set to display
                break;
            case "Not Visible":
                break;
            default:
                System.out.println("Unexpected value for configuration option " + name
                        + ". Value: " + c_cd.getData(name));
                break;
        }

    }

    private void setSubmodelOutputVisiblity() {
        ArrayList<JComponent> list = new ArrayList<JComponent>();
        list.add(jLabel4);
        list.add(jLabel5);
        list.add(jLabel6);
        list.add(jLabel7);
        list.add(jLabel8);
        list.add(jLabel9);

        list.add(JTF_hydrology);
        list.add(JTF_soil);
        list.add(JTF_management);
        list.add(JTF_crop);
        list.add(JTF_decomposition);
        list.add(JTF_erosion);

        if (c_cd.getData("CD-Submodel outputs").equals("readOnly")
                || c_cd.getData("CD-Submodel outputs").equals("notVisible")) {
            for (JComponent jc : list) {
                jc.setEnabled(false);
            }
        }
        if (c_cd.getData("CD-Submodel outputs").equals("notVisible")) {
            for (JComponent jc : list) {
                jc.setVisible(false);
            }
        }
    }

    /**
     *
     * @return
     */
    public boolean getCreateBatch() {
        return JCB_batchMode.isSelected();
    }

    /**
     * Getter for the created run file.
     * @return File object representing the new run.
     */
    public TFile getRunFile() {
        return c_runFile;
    }

    /**
     * Getter for the run location.
     * @return Returns the parent file object from the getRunFile() method.
     */
    public TFile getRunLocation() {
        return new TFile(getRunFile().getParentFile());
    }

    /**
     * Combines the values in the text fields and the run suffix to create a new
     * File object representing the run.  The suffix is a constant in the RunFileData 
     * class.
     * 
     * RunLocatino + RunName + Suffix
     */
    private void buildRunFile() {
        c_runFile = new TFile(JTF_runlocation.getText().trim(),
                JTF_runname.getText().trim() + RunFileData.RunSuffix);
        JTF_runfile.setText(c_runFile.getPath());
        checkForErrors();
    }

    /**
     * Checks the built run File object for errors.  If an error is found a message is
     * displayed on the RunNameChooser dialog.  The dialog icon is also changed to
     * indicate an error, and the Ok button is disabled when there is an error.
     * 
     * Errors:
     * 1. Run name is required.
     * 2. Run location is required.
     * 3. The run file cannot already exist.
     * 4. Run name contains invalid characters.
     */
    private void checkForErrors() {
        boolean hasError = false;
        if (c_runFile == null) {
            return;
        } else if (JTF_runname.getText().trim().length() == 0) {
            //run name is required
            setError("A run name is required.");
            hasError = true;
        } else if (JTF_runlocation.getText().trim().length() == 0) {
            //run location is required
            setError("A run location is required.");
            hasError = true;
        } else if (c_runFile.exists()) {
            //The file already exists
            if (c_runFile.getName().length() > 50) {
                setError("Run name already exists.  Please choose another.");
            } else {
                setError("Run '" + c_runFile.getName() + "' already exists.");
            }
            hasError = true;
        } else if (checkInvaildChar(JTF_runname.getText().trim())) {
            //The file is not a valid name
            setError("Invalid character in run name.");
            hasError = true;
        }

        if (Util.isWindows()) {
            String lengthError = checkFileLengths(new TFile(JTF_runfile.getText()));
            if (lengthError != null) {
                setError(lengthError);
                hasError = true;
            }
        }

        if (!hasError) {
            setError("");
        }

        if (c_jop != null) {
            if (hasError) {
                c_jop.setMessageType(JOptionPane.ERROR_MESSAGE);
            } else {
                c_jop.setMessageType(JOptionPane.INFORMATION_MESSAGE);
            }
        }

        //Fix focus loss issue
        if (c_lastFocus != null) {
            c_lastFocus.requestFocusInWindow();
        }

        c_okButton = (JButton) findOptionButton(c_jop, "Ok");

        if (c_okButton != null) {
            c_okButton.setEnabled(!hasError);
            c_jop.revalidate();
            c_jop.repaint();
        }

    }

    private String checkFileLengths(TFile run) {
        //check file lengths of the man,ifc
        String runPath = run.getAbsolutePath();

        //run location
        if (runPath.length() + 25 > Util.MAX_WINDOWS_FILEPATH_LENGTH) {
            return "Run location invalid.  Run name too long.";
        }

        //man file
        String manPath = c_rfd.getData(RunFileData.ManageFile);
        if (manPath != null) {
            TFile manFile = new TFile(manPath);
            String manName = manFile.getName();
            if (runPath.length() + manName.length() + 1 > Util.MAX_WINDOWS_FILEPATH_LENGTH) {
                return "Management file invalid. Filename too long.";
            }
        }

        //ifc file
        String ifcPath = c_rfd.getData(RunFileData.SoilFile);
        if (ifcPath != null) {
            TFile ifcFile = new TFile(ifcPath);
            String ifcName = ifcFile.getName();
            if (runPath.length() + ifcName.length() + 1 > Util.MAX_WINDOWS_FILEPATH_LENGTH) {
                return "Soil file invalid. Filename too long.";
            }
        }

        return null;
    }

    /**
     * If the error message does not equal the one already being displayed, the new
     * message is displaye in the error label.
     * @param error Error message to display.
     */
    private void setError(String error) {
        //only update if the text changed            
        if (JL_error.getText().equals(error) == false) {
            JL_error.setText(error);
        }

    }

    /**
     * Creates and displays a JOptionPane with 'this' as the message.  The result is
     * parsed and returned to the user.  If the user clicks Ok the run location will 
     * be checked for existince.  The user will be prompted to confirm the creation
     * of the run location.  If the user denies the run location creation the main
     * dialog will be displayed again.
     * @param parent Component which owns should the dialog box.  Used for the modal
     * behavior.
     * @return File object representing the run to create.  Null is return if the 
     * user did not click Ok.
     */
    public TFile showRunNameChooser(Component parent) {
        //return JOptionPane.showOptionDialog(parent, this, "WEPS Run Name", JOptionPane.YES_OPTION
        //+ JOptionPane.CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, null, null, null);                      
        c_jop = new JOptionPane(this);
        c_jop.setMessageType(JOptionPane.INFORMATION_MESSAGE);
        String[] options = {"Ok", "Cancel"};
        c_jop.setOptions(options);
        JDialog dialog = c_jop.createDialog(parent, "WEPS Run Name");
        checkForErrors();

        dialog.setVisible(true);
        Object result = c_jop.getValue();
        dialog.dispose();

        if (result == options[0]) {
            //ok
            if (validateParentStructure(getRunFile())) {
                return getRunFile();
            } else {
                return showRunNameChooser(parent);
            }
        } else {
            //cancel
            return null;
        }
    }

    /**
     * The file is check for existince.  If it does not exist the user will be 
     * prompted to confirm the creation of the run location.  If the user denies the 
     * run location creation the main dialog will be displayed again.
     * @param file File object representing the run location to create.
     * @return Returns true if the location directory and all required parent
     * directories have been created.
     */
    private boolean validateParentStructure(TFile file) {
        TFile parent = new TFile(file.getParentFile());
        if (parent.exists() == false) {
            int result = JOptionPane.showConfirmDialog(this,
                    "The run location does not exist.  Would you like to create it now?\n\n"
                    + parent.getPath(), "WEPS Run Name", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            if (result == JOptionPane.YES_OPTION) {
                return parent.mkdirs();
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * Recursive method for locating a button.  Looks through each of the components
     * in the comp object.  If the component is a JButton it tests the text and returns
     * if matching the testValue.  If no button is found then this method is called
     * for each child container of the comp object.
     * @param comp Component to search for the button down from.
     * @param testValue String value to test the text of buttons with.
     * @return Button with text matching the testValue.  If no button is found the 
     * method returns null.
     */
    private Component findOptionButton(Component comp, String testValue) {
        if (comp instanceof JButton) {
            JButton button = (JButton) comp;
            if (button.getText().equals(testValue)) {
                return comp;
            }
        }
        if (comp instanceof Container) {
            Component[] components = ((Container) comp).getComponents();
            for (Component component : components) {
                Component child = findOptionButton(component, testValue);
                if (child != null) {
                    return child;
                }
            }
        }
        return null;
    }

    /**
     * Tests a directory name for invalid characters.
     * @param newDir Directort name to test.
     * @return True if newDir contains invalid characters.
     */
    private static boolean checkInvaildChar(String newDir) {
        char[] invalidChars = {'\\', '/', '<', '>', '|', '?', '*', '&', '"', ':', '~', '`'};
        //A single quote cannot be given as a character, so given as a separate string
        String singleQuote = "'";
        boolean invalidPresent = false;
        for (int i = 0; i < newDir.length(); i++) {
            for (int j = 0; j < invalidChars.length; j++) {
                if (newDir.charAt(i) == invalidChars[j] || newDir.charAt(i) == singleQuote.charAt(0)) {
                    invalidPresent = true;
                    break;
                }
            }

        }
        return invalidPresent;
    }

    @Override
    protected void setOutput(SubmodelOutput.submodel s) {
        c_rfd.so.setOutput(s);
    }

    @Override
    protected void checkboxEvent(SubmodelOutput.outFile f, boolean value) {
        c_rfd.so.setChecked(f, value);
    }

    /**
     * Handles action events for the Browse button.  A WepsFileChooser is used to 
     * select a directory for the run location.
     * @param evt ActionEvent from the browse button.
     */
    //Browse button event
    @Override
    protected void JB_browse_actionPerformed(java.awt.event.ActionEvent evt) {
        WepsFileChooser wfc = new WepsFileChooser(WepsFileChooser.Filetype.RUNDIR,
                getRunLocation().getParent(), WepsFileChooser.SELECT);
        wfc.setAccessory(new ResetRunLocationPanel(c_cd, c_rfd, wfc));
        wfc.setSelectedFile(getRunLocation());
        int result = wfc.showDialog(this);
        if (result == WepsFileChooser.APPROVE_OPTION) {
            JTF_runlocation.setText(wfc.getSelectedFile().getPath());
            JTF_runlocation.requestFocusInWindow();
            c_rfd.setData(RunFileData.RunsLocation, getRunLocation().getAbsolutePath());
        }
    }

    //Document listener event
    /**
     * Calls buildRunFile() when a textfield's content changes.
     * @param e DocumentEvent from the changed text field.
     */
    @Override
    public void changedUpdate(DocumentEvent e) {
        buildRunFile();
    }

    /**
     * Calls buildRunFile() when a textfield's content changes.
     * @param e DocumentEvent from the changed text field.
     */
    @Override
    public void insertUpdate(DocumentEvent e) {
        buildRunFile();
    }

    /**
     * Calls buildRunFile() when a textfield's content changes.
     * @param e DocumentEvent from the changed text field.
     */
    @Override
    public void removeUpdate(DocumentEvent e) {
        buildRunFile();
    }

    //Focus Listener event
    /**
     * Sets the c_lastFocus variable to the source component of the FocusEvent.
     * @param e FocusEvent
     */
    @Override
    public void focusGained(FocusEvent e) {
        c_lastFocus = e.getComponent();
    }

    /**
     * Not used.  Required by FocusListener interface.
     * @param e Focus Event
     */
    @Override
    public void focusLost(FocusEvent e) {

    }

    //Action events
    /**
     * Action event handler to click the Ok button when the user presses [Enter] from 
     * the run name or run location text fields.
     * @param e ActionEvent
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (c_okButton != null) {
            c_okButton.doClick();
        }
    }

    /**
     *
     * @param e
     */
    @Override
    public void propertyChange(PropertyChangeEvent e) {
        String property = e.getPropertyName();
        String value = e.getNewValue().toString();
        switch (property) {
            case RunFileData.LastRunAttempt:
                c_lastAttemptedRun = new TFile(value).getName();
                break;
            case RunFileData.RunsLocation:
                if (value.equals("NULL")) {
                    value = ConfigData.getDefault().getData(ConfigData.DefaultRunsLocation);
                }
                c_runsLocation = value;
                break;
            case ConfigData.DefaultRunsLocation:
                c_defaultRunsLocation = value;
                break;
            case ConfigData.AllowScriptCreation:
                if ("1".equals(value)) {
                    JCB_batchMode.setVisible(true);
                } else {
                    JCB_batchMode.setVisible(false);
                    JCB_batchMode.setSelected(false);
                }
                break;
        }
    }
}
