/*
 * Copyright (c) 2015 Paul Scherrer Institute. All rights reserved.
 */
//<editor-fold defaultstate="collapsed" desc="import"> 

import ch.psi.pshell.core.Context;
import ch.psi.pshell.scripting.StatementException;
import ch.psi.pshell.security.AccessLevel;
import ch.psi.pshell.ui.App;
import ch.psi.pshell.ui.Panel;
import ch.psi.pshell.ui.Plugin;
import ch.psi.pshell.ui.View;
import ch.psi.utils.Convert;
import ch.psi.utils.swing.SwingUtils;
import ch.psi.utils.swing.SwingUtils.OptionType;
import java.awt.Component;
import java.awt.Point;
import java.io.BufferedReader;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.table.DefaultTableModel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import java.util.function.Consumer;
import java.util.logging.FileHandler;
import java.util.logging.SimpleFormatter;
import java.util.regex.PatternSyntaxException;
import javax.script.ScriptException;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.RowFilter;
import javax.swing.filechooser.FileSystemView;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import org.apache.commons.io.FilenameUtils;
import org.python.core.PyList;
import static org.python.bouncycastle.util.Arrays.append;

//</editor-fold>

/**
 *
 * @author boccioli_m
 */
public class TestingList extends Panel {
    Properties properties = new Properties();


    public TestingList() throws Exception {
        initialise();
    }
    
//<editor-fold defaultstate="collapsed" desc="types enum"> 
    /**
     * enumeration of table column indexes
     */
    public enum COL {

        CHECK(0),
        STARTSEQUENCE(1),
        TIME(2),
        DEVICENAME(3),
        DEVICEDESCR(4),
        TESTSUITE(5),
        TESTNAME(6),
        TESTPATH(7),
        TESTPARAMS(8),
        TESTDESCR(9),
        TESTHELP(10),
        RESULT(11),
        STATUS(12),
        ICON(13);
        private int value;

        private COL(int value) {
            this.value = value;
        }

        /**
         *
         * @return int index of the corresponding column
         */
        public int index() {
            return (int) value;
        }
    };

    /**
     * enumeration of possible test statuses: text and related icon
     */
    public enum TestStatus {

        SUCCESS,
        FAILURE,
        PENDING,
        DISABLED,
        RUNNING;

        /**
         *
         * @return
         */
        public String IconFilename() {
            String iconFileName = "";
            switch (this) {
                case SUCCESS:
                    iconFileName = "/icons/button_ok-16px.png";
                    break;
                case FAILURE:
                    iconFileName = "/icons/button_close-16px.png";
                    break;
                case PENDING:
                    iconFileName = "/icons/button_stop-16px.png";
                    break;
                case DISABLED:
                    iconFileName = "";
                    break;
                case RUNNING:
                    iconFileName = "/icons/button_play-16px.png";
                    break;
            }
            return iconFileName;
        }

        @Override
        public final String toString() {
            String status = "";
            switch (this) {
                case SUCCESS:
                    status = "Success";
                    break;
                case FAILURE:
                    status = "Failure";
                    break;
                case PENDING:
                    status = "Pending";
                    break;
                case DISABLED:
                    status = "Disabled";
                    break;
                case RUNNING:
                    status = "Running";
                    break;
            }
            return status;
        }

        /**
         *
         * @return ImageIcon corresponding to the selected testStatus
         */
        public ImageIcon Icon() {
            String iconFileName = this.IconFilename();
            ImageIcon icon = null;
            if (iconFileName != "") {
                icon = new ImageIcon(getClass().getResource(this.IconFilename()));
            }
            return icon;
        }
    };

    /**
     * Sequence options: AFTER: the test starts after the test above has
     * finished TOGETHER: the test starts at the same time as the test above
     */
    public enum StartSequence {

        AFTER,
        TOGETHER;

        @Override
        public final String toString() {
            String status = "";
            switch (this) {
                case AFTER:
                    status = "After previous";
                    break;
                case TOGETHER:
                    status = "With previous";
                    break;
            }
            return status;
        }
    };

    /**
     * List of Keys defining the arguments to be passed to a test script
     */
    public enum testArgNames {

        PARAMETERS,
        TEST,
        DEVICE,
        TEST_PATH,
        STATUS;

        @Override
        public final String toString() {
            String status = "";
            switch (this) {
                case PARAMETERS:
                    status = "parameters";
                    break;
                case TEST:
                    status = "test";
                    break;
                case DEVICE:
                    status = "device";
                    break;
                case TEST_PATH:
                    status = "testPath";
                    break;
                case STATUS:
                    status = "status";
                    break;
            }
            return status;
        }
    };

    /**
     * List of Keys defining the arguments to be passed to a test script
     */
    public enum testPropertyNames {

        DEVICE_NAME,
        DEVICE_DESCRIPTION,
        TEST_NAME,
        TEST_DESCRIPTION,
        TEST_PARAMETERS,
        TEST_HELP;

        @Override
        public final String toString() {
            String val = "";
            switch (this) {
                case DEVICE_NAME:
                case TEST_NAME:
                    val = "name";
                    break; 
                case TEST_PARAMETERS:
                    val = "parameters";
                    break;
                case DEVICE_DESCRIPTION:
                case TEST_DESCRIPTION:
                    val = "description";
                    break;
                case TEST_HELP:
                    val = "help";
                    break;
            }
            return val;
        }
    };
//</editor-fold>
    
    Logger logger = Logger.getLogger("TestsLog");
    //these paths are converted to unix or win path according to host OS   
    public static Path TESTS_DEVICES_DEFAULT_DIR = Paths.get(".", "home", "script", "tests", "devices");
    public static Path TESTS_TESTS_DEFAULT_DIR = Paths.get(".", "home", "script", "tests", "tests");
    public static Path TESTS_CONFIG_DEFAULT_DIR = Paths.get(".", "home", "script", "tests", "config");
    public static Path TESTS_PROPERTIES_DEFAULT_DIR = Paths.get(".", "home", "script", "tests", "tests.properties");
    public final  Path TESTS_LOG_DEFAULT_DIR = Paths.get(".", "home", "script", "tests", "log", "TestsLog" + getnow() + ".txt");
    public static String TESTS_CONFIG_FILENAME = ".config";
    public static String TESTS_HELP_FILENAME = "help.html";

    
    /**
     * initialise panel
     */
    private void initialise(){
        initComponents(); 
        initLogger();
        buildTable();

        try {
            loadTests();
            //if not administrator, then show only enabled tests
            boolean bShow = getContext().getInstance().getLevel()==AccessLevel.administrator;
            jCheckBoxMenuShowSelectedTests1.setEnabled(bShow);
        } catch (Exception ex) {
            getLogger().severe(ex.getMessage());
        }   
        loadProperties();        
    }
  
    
    /**
     * load general properties
     */
    private void loadProperties(){
        File configFile = TESTS_PROPERTIES_DEFAULT_DIR.toFile();
        if (!configFile.isFile()) return;
        boolean error = false;
        try {
            FileReader reader = new FileReader(configFile);
            Properties props = new Properties();
            props.load(reader);
            reader.close();
            String property;
            properties = props;
            
            //custom panel
            property = props.getProperty("customPanel");
            //System.out.println("load customPanel: " + property);
            property = property.replace("\"","");
            loadCustomPanel(property);
            
            //list filter
            property = props.getProperty("listFilter");
            //System.out.println("load listFilter: " + property);
            if(property==null || property==""){
                loadListFilter();                
            } else{
                property = property.replace("\"","");
                loadListFilter(Paths.get(TESTS_CONFIG_DEFAULT_DIR.toString(), property).toFile());
            }

            //show Enabled Tests Only
            property = props.getProperty("showEnabledTestsOnly");
            //System.out.println("load showEnabledTestsOnly: " + property);
            property = property.replace("\"","");
            showEnabledTestsOnly("true".equals(property));


        } catch (FileNotFoundException ex) {
            // file does not exist
            System.out.println(ex.toString());
            error = true;
        } catch (IOException ex) {
            // I/O error
            System.out.println(ex.toString());
            error = true;
        } catch (Exception ex){
            // Any other exception
            System.out.println(ex.toString());  
            error = true;            
        }       
        if(error){
            //load defaults
        }
    }
    
       
    /**
     * save general properties
     */
    private void saveProperties(String key, String val){
        File configFile = TESTS_PROPERTIES_DEFAULT_DIR.toFile();    

        try {
            if (!configFile.isFile()) configFile.createNewFile();
            FileReader reader = new FileReader(configFile);
            //System.out.println("save property: "+ key + " = " + val);
            properties.setProperty(key, val);
            Properties props = properties;
            
            OutputStream out = new FileOutputStream( configFile );
            props.store(out, "TestingList for pshell: configuration properties");

            out.close();
        } catch (FileNotFoundException ex) {
            // file does not exist
            System.out.println(ex.toString());
        } catch (IOException ex) {
            // I/O error
            System.out.println(ex.toString());
        }    

    }
    
    
    /**
     * close the custom panel if present    
    */
    private void closeCustomPanel(){
        jPanelCustomFrame.removeAll();
        jPanelCustom.setVisible(false);
        repaint();
        //store custom panel in the properties
        saveProperties("customPanel", "");
    }
    
    
    /**
     * load plugins available excluding the plugins used directly by TestingList
     * @return list of plugins assumed to be custom panels
     */
    private List<String> getCustomPanels(){
        Path pluginsPath = Paths.get(".", "home", "config", "plugins.properties");
        List<String> stringBuffer =new ArrayList<String>() ;
        try {
                //get list of java classes from plugins list file
                File file = pluginsPath.toFile();
                FileReader fileReader = new FileReader(file);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                String line, fileName;
                while ((line = bufferedReader.readLine()) != null) {
                    if(line.contains("=enabled")){
                        //extract java class name from file path
                        fileName = line.replace(".java=enabled", "");
                        fileName = (Paths.get(fileName)).getFileName().toString();
                        if( !(fileName.contains("TestingListDetails") ||
                            fileName.contains("TestingList") ||
                            fileName.contains("NewTest"))){
                                stringBuffer.add(fileName);
                        }
                    }
                }
                fileReader.close();
                //System.out.println("Contents of file:");
                //System.out.println(stringBuffer.toString());
        } catch (IOException e) {
                e.printStackTrace();
        }        
        return stringBuffer;
    }
    
    
    /**
     * get the presently active custom panel
     * 
     * @return custom panel as component
     */
    public Component getCustomPanel(){
        Component customPanel = null;
        if(this.jPanelCustomFrame.getComponentCount() >0){
            Component components[] = this.jPanelCustomFrame.getComponents();
            //there is only one component
            customPanel =  components[0];
        }
        return customPanel;
    }

  
    /**
     * add custom panel if specified. 
     * Custom panels are java classes. To add a new custom panel MyNewPanel:
     * - create a panel and save it as MyNewPanel.java
     * - add MyNewPanel.java file in the pshell plugins folder
     * - on pshell, open Plugins and enable MyNewPanel.java
     * - restart pshell
     * To show the new panel, open the menu Advanced/Load Custom Panel
     * 
     * @param panel the panel to add as component
     */
    public void loadCustomPanel(Component panel){  
        if(panel != null)
            loadCustomPanel(panel.getClass().getName() );
        else
            closeCustomPanel();            
    }      
    
    /**
     * add custom panel if specified. 
     * Custom panels are java classes. To add a new custom panel MyNewPanel:
     * - create a panel and save it as MyNewPanel.java
     * - add MyNewPanel.java file in the pshell plugins folder
     * - on pshell, open Plugins and enable MyNewPanel.java
     * - restart pshell
     * To show the new panel, open the menu Advanced/Load Custom Panel
     * 
     * @param sPanelClassName the panel to add as class name
     */
    private void loadCustomPanel(String sPanelClassName){        
        closeCustomPanel();
        if (    sPanelClassName == null || 
                sPanelClassName.trim() == "" || 
                sPanelClassName.isEmpty()) {
            return;
        }

        //if a custom panel is shown, the tests cannot be run in parallel,
        //they can only be run in series (one after the other).     
        boolean changeSequenceToAfter = true;// = SwingUtils.OptionResult.No;
        for (int row = 0; row < jTable1.getRowCount(); row++) {
            try{
                if(jTable1.getValueAt(row, COL.STARTSEQUENCE.ordinal())==null) continue;
                String sStartSequence = String.valueOf(jTable1.getValueAt(row, COL.STARTSEQUENCE.ordinal()));   
                if(sStartSequence.equals(StartSequence.TOGETHER.toString())){
                    SwingUtils.OptionResult ret = SwingUtils.showOption(this, "Modify Start mode", 
                            "A custom panel is active: tests can only run in sequence (Start mode: 'After previous'). "+
                            "\nDo you want to keep the custom panel open and set all tests Start mode to 'After previous'?", 
                           OptionType.YesNo);
                    if(ret == SwingUtils.OptionResult.Yes){
                        changeSequenceToAfter = true ;
                    } else {
                        changeSequenceToAfter = false ;
                    }     
                    break;
                }
            } catch (NullPointerException ex){
                continue;
            }            
        }
        //Set all active tests sequence to "After previous"
        if(changeSequenceToAfter ){
            for (int row = 0; row < jTable1.getRowCount(); row++) {
                boolean bSelected = (boolean) jTable1.getValueAt(row, COL.CHECK.ordinal());
                if(bSelected){
                    jTable1.setValueAt(StartSequence.AFTER.toString(), row, COL.STARTSEQUENCE.ordinal());
                }
            }
        } else { //user does not wat to change running sequence from parallel to sequential
            return;
        } 
    
        try {
            //create a class to visualise the details panel
            Class panelClass = getContext().getClassByName(sPanelClassName);
            JPanel detailsPanel = (JPanel) panelClass.getConstructor(new Class[]{String.class}).newInstance(new Object[]{""});    
            this.jPanelCustomFrame.add(detailsPanel);
            this.jLabelCustomPanelName.setText("Custom panel: "+sPanelClassName);
            repaint();
            revalidate();
            //store custom panel in the properties
            saveProperties("customPanel", sPanelClassName);
            jPanelCustom.setVisible(true);
            animateCustomPanel();
        } catch (Exception ex) {
            System.out.println("animateCustomPanel(): "+String.valueOf(ex));
        }
    }
    
    /**
     * Starts animating the custom panel, if present
     * 
     * Note: the custom panel must have the public method "animate(String deviceName)"
     * 
     * @param deviceName name of the device. The panel should then append the epics PV to the device name for caget/caset
     */
    public void animateCustomPanel(String deviceName) {
        try {
            Component customPanel =  getCustomPanel();
            //if no custom panel: nothing to do
            if(customPanel==null) return;            
            //call the method "animate" of the custom panel (this function must be there)
            Class[] cArg = new Class[1];
            cArg[0] = String.class;
            Method animate = customPanel.getClass().getMethod("animate", cArg);
            animate.invoke(customPanel, deviceName);
        } catch (Exception ex) {
            System.out.println("animateCustomPanel(): "+String.valueOf(ex));
        }
    }
    
    
    /**
     * Starts animating the custom panel, if present, and only if the whole list of selected tests contains one only device name
     * 
     * Note: the custom panel must have the public method "animate(String deviceName)"
     */    
    public void animateCustomPanel(){
        Component customPanel =  getCustomPanel();
        //if no custom panel: nothing to do
        if(customPanel==null) return;          
        //scan the table to see if only one device name is present
        String sDeviceName = "";
        String sRowDeviceName = "";
        boolean bRowTestEnabled = false;
        for (int row = 0; row < jTable1.getRowCount(); row++) {
            sRowDeviceName  = String.valueOf(jTable1.getValueAt(row, COL.DEVICENAME.ordinal()));
            bRowTestEnabled = (boolean) jTable1.getValueAt(row, COL.CHECK.ordinal());
            if(bRowTestEnabled){
                if(sDeviceName == ""){
                    sDeviceName = sRowDeviceName;
                } else if(sDeviceName != sRowDeviceName){
                    //there are at least two devices. Cannot animate the custom panel
                    return;
                }
            }
        }
        //if only one device name is present in the table, then animate the custom panel with the device
        if(sDeviceName != ""){
            animateCustomPanel(sDeviceName);
        }
    }
    
    
    /**
     * write info into a log file
     * @param text the info to be logged
     */
    private void log(Object text) {
        try {
            getContext().getDataManager().appendLog(String.valueOf(text));
        } catch (Exception ex) {
            System.out.println("animateCustomPanel(): "+String.valueOf(ex));
        }
    }

    /**
     * initialise the logger
     */
    private void initLogger() {
        /*
        this.evalAsync("bla").handle((ok, ex) -> {            
            if (ex != null) {
                
            } else {
            }
            return ok;
        });
        */

        try {

            FileHandler fh;
            // This block configure the logger with handler and formatter  
            fh = new FileHandler(TESTS_LOG_DEFAULT_DIR.toString());
            logger.addHandler(fh);
            SimpleFormatter formatter = new SimpleFormatter();
            fh.setFormatter(formatter);
            log("INFO - " + "New testing session");
        } catch (SecurityException e) {
            SwingUtils.showMessage(this, "initLogger()", e.toString());
        } catch (IOException e) {
            SwingUtils.showMessage(this, "initLogger()", e.toString());
        }
    }

    /**
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")

    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jPopupMenuOptions = new javax.swing.JPopupMenu();
        jCheckBoxMenuShowSelectedTests = new javax.swing.JCheckBoxMenuItem();
        jMenuItemReload = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        jMenuAdvanced = new javax.swing.JMenu();
        jMenuItemNewTest = new javax.swing.JMenuItem();
        jMenuItemNewDevice = new javax.swing.JMenuItem();
        jMenuItemEditScript = new javax.swing.JMenuItem();
        jSeparator6 = new javax.swing.JPopupMenu.Separator();
        jMenuLoadCustomPanel = new javax.swing.JMenu();
        jSeparator2 = new javax.swing.JPopupMenu.Separator();
        jMenuItemOpenLog = new javax.swing.JMenuItem();
        jPopupMenuConfigs = new javax.swing.JPopupMenu();
        jPopupMenuTable = new javax.swing.JPopupMenu();
        jMenuItemShowDetails = new javax.swing.JMenuItem();
        jSeparator5 = new javax.swing.JPopupMenu.Separator();
        jMenuItemRunSingleTest = new javax.swing.JMenuItem();
        jSeparator3 = new javax.swing.JPopupMenu.Separator();
        jMenuSelect = new javax.swing.JMenu();
        jMenuItemSelectAll = new javax.swing.JMenuItem();
        jMenuItemSelectSelection = new javax.swing.JMenuItem();
        jMenuItemSelectNone = new javax.swing.JMenuItem();
        jMenuItemDeselectSelection = new javax.swing.JMenuItem();
        jSeparator7 = new javax.swing.JPopupMenu.Separator();
        jMenuItemDeselect = new javax.swing.JMenuItem();
        jSeparator4 = new javax.swing.JPopupMenu.Separator();
        jCheckBoxMenuShowSelectedTests1 = new javax.swing.JCheckBoxMenuItem();
        jMenuItemReload1 = new javax.swing.JMenuItem();
        jPopupMenuCustom = new javax.swing.JPopupMenu();
        jMenuItemCustomClose = new javax.swing.JMenuItem();
        jPanel1 = new javax.swing.JPanel();
        jPanelTopCommands = new javax.swing.JPanel();
        jPanelButtons = new javax.swing.JPanel();
        jButtonOptions = new javax.swing.JButton();
        jButtonSave = new javax.swing.JButton();
        jButtonOpen = new javax.swing.JButton();
        jButtonRun = new javax.swing.JButton();
        jPanelSelection = new javax.swing.JPanel();
        jCheckBoxEnableDisable = new javax.swing.JCheckBox();
        jButtonMoveDown = new javax.swing.JButton();
        jButtonMoveUp = new javax.swing.JButton();
        jScrollPane2 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();
        jPanelCustom = new javax.swing.JPanel();
        jPanelCustomHeader = new javax.swing.JPanel();
        jButtonX = new javax.swing.JButton();
        jLabelCustomPanelName = new javax.swing.JLabel();
        jPanelCustomFrame = new javax.swing.JPanel();

        jCheckBoxMenuShowSelectedTests.setSelected(true);
        jCheckBoxMenuShowSelectedTests.setText("Show enabled tests only");
        jCheckBoxMenuShowSelectedTests.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBoxMenuShowSelectedTestsActionPerformed(evt);
            }
        });
        jPopupMenuOptions.add(jCheckBoxMenuShowSelectedTests);

        jMenuItemReload.setText("Reload tests");
        jMenuItemReload.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemReloadActionPerformed(evt);
            }
        });
        jPopupMenuOptions.add(jMenuItemReload);
        jPopupMenuOptions.add(jSeparator1);

        jMenuAdvanced.setText("Advanced");

        jMenuItemNewTest.setText("New Test...");
        jMenuItemNewTest.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemNewTestActionPerformed(evt);
            }
        });
        jMenuAdvanced.add(jMenuItemNewTest);

        jMenuItemNewDevice.setText("New Device...");
        jMenuItemNewDevice.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemNewDeviceActionPerformed(evt);
            }
        });
        jMenuAdvanced.add(jMenuItemNewDevice);

        jMenuItemEditScript.setText("Edit selected test");
        jMenuItemEditScript.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemEditScriptActionPerformed(evt);
            }
        });
        jMenuAdvanced.add(jMenuItemEditScript);
        jMenuAdvanced.add(jSeparator6);

        jMenuLoadCustomPanel.setText("Load Custom  Panel");
        jMenuLoadCustomPanel.addMenuListener(new javax.swing.event.MenuListener() {
            public void menuCanceled(javax.swing.event.MenuEvent evt) {
            }
            public void menuDeselected(javax.swing.event.MenuEvent evt) {
            }
            public void menuSelected(javax.swing.event.MenuEvent evt) {
                jMenuLoadCustomPanelMenuSelected(evt);
            }
        });
        jMenuAdvanced.add(jMenuLoadCustomPanel);
        jMenuAdvanced.add(jSeparator2);

        jMenuItemOpenLog.setText("Open Log");
        jMenuItemOpenLog.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemOpenLogActionPerformed(evt);
            }
        });
        jMenuAdvanced.add(jMenuItemOpenLog);

        jPopupMenuOptions.add(jMenuAdvanced);

        jMenuItemShowDetails.setText("Open Test Details");
        jMenuItemShowDetails.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemShowDetailsActionPerformed(evt);
            }
        });
        jPopupMenuTable.add(jMenuItemShowDetails);
        jPopupMenuTable.add(jSeparator5);

        jMenuItemRunSingleTest.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/play-circled-16-000000.png"))); // NOI18N
        jMenuItemRunSingleTest.setText("Run this test now");
        jMenuItemRunSingleTest.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemRunSingleTestActionPerformed(evt);
            }
        });
        jPopupMenuTable.add(jMenuItemRunSingleTest);
        jPopupMenuTable.add(jSeparator3);

        jMenuSelect.setText("Enable/Disable");

        jMenuItemSelectAll.setText("Enable all");
        jMenuItemSelectAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemSelectAllActionPerformed(evt);
            }
        });
        jMenuSelect.add(jMenuItemSelectAll);

        jMenuItemSelectSelection.setText("Enable selection");
        jMenuItemSelectSelection.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemSelectSelectionActionPerformed(evt);
            }
        });
        jMenuSelect.add(jMenuItemSelectSelection);

        jMenuItemSelectNone.setText("Disable all");
        jMenuItemSelectNone.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemSelectNoneActionPerformed(evt);
            }
        });
        jMenuSelect.add(jMenuItemSelectNone);

        jMenuItemDeselectSelection.setText("Disable selection");
        jMenuItemDeselectSelection.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemDeselectSelectionActionPerformed(evt);
            }
        });
        jMenuSelect.add(jMenuItemDeselectSelection);
        jMenuSelect.add(jSeparator7);

        jMenuItemDeselect.setText("Deselect all");
        jMenuItemDeselect.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemDeselectActionPerformed(evt);
            }
        });
        jMenuSelect.add(jMenuItemDeselect);

        jPopupMenuTable.add(jMenuSelect);
        jPopupMenuTable.add(jSeparator4);

        jCheckBoxMenuShowSelectedTests1.setText("Show enabled tests only");
        jCheckBoxMenuShowSelectedTests1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBoxMenuShowSelectedTests1ActionPerformed(evt);
            }
        });
        jPopupMenuTable.add(jCheckBoxMenuShowSelectedTests1);

        jMenuItemReload1.setText("Reload tests");
        jMenuItemReload1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemReload1ActionPerformed(evt);
            }
        });
        jPopupMenuTable.add(jMenuItemReload1);

        jMenuItemCustomClose.setText("Close this custom panel");
        jMenuItemCustomClose.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemCustomCloseActionPerformed(evt);
            }
        });
        jPopupMenuCustom.add(jMenuItemCustomClose);

        setLayout(new java.awt.BorderLayout());

        jPanel1.setMinimumSize(new java.awt.Dimension(100, 200));
        jPanel1.setPreferredSize(new java.awt.Dimension(110, 110));
        jPanel1.setLayout(new java.awt.BorderLayout());

        jPanelTopCommands.setMinimumSize(new java.awt.Dimension(115, 100));
        jPanelTopCommands.setLayout(new java.awt.BorderLayout());

        jPanelButtons.setMinimumSize(new java.awt.Dimension(0, 80));
        jPanelButtons.setPreferredSize(new java.awt.Dimension(100, 80));
        jPanelButtons.setLayout(new java.awt.BorderLayout());

        jButtonOptions.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/settings-32px.png"))); // NOI18N
        jButtonOptions.setToolTipText("Optional actions");
        jButtonOptions.setMaximumSize(new java.awt.Dimension(60, 40));
        jButtonOptions.setMinimumSize(new java.awt.Dimension(0, 40));
        jButtonOptions.setPreferredSize(new java.awt.Dimension(50, 40));
        jButtonOptions.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonOptionsActionPerformed(evt);
            }
        });
        jPanelButtons.add(jButtonOptions, java.awt.BorderLayout.LINE_START);

        jButtonSave.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/android-save-24-000000.png"))); // NOI18N
        jButtonSave.setToolTipText("Save current configuration");
        jButtonSave.setMaximumSize(new java.awt.Dimension(60, 40));
        jButtonSave.setMinimumSize(new java.awt.Dimension(0, 40));
        jButtonSave.setPreferredSize(new java.awt.Dimension(50, 40));
        jButtonSave.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonSaveActionPerformed(evt);
            }
        });
        jPanelButtons.add(jButtonSave, java.awt.BorderLayout.LINE_END);

        jButtonOpen.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/folder-open-24-000000.png"))); // NOI18N
        jButtonOpen.setToolTipText("Open configuration");
        jButtonOpen.setMinimumSize(new java.awt.Dimension(0, 9));
        jButtonOpen.setPreferredSize(new java.awt.Dimension(33, 50));
        jButtonOpen.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonOpenActionPerformed(evt);
            }
        });
        jPanelButtons.add(jButtonOpen, java.awt.BorderLayout.PAGE_END);

        jPanelTopCommands.add(jPanelButtons, java.awt.BorderLayout.EAST);

        jButtonRun.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/play-circled-64-000000.png"))); // NOI18N
        jButtonRun.setToolTipText("Run enabled tests");
        jButtonRun.setMaximumSize(new java.awt.Dimension(330000, 39));
        jButtonRun.setMinimumSize(new java.awt.Dimension(90, 90));
        jButtonRun.setPreferredSize(new java.awt.Dimension(90, 39));
        jButtonRun.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonRunActionPerformed(evt);
            }
        });
        jPanelTopCommands.add(jButtonRun, java.awt.BorderLayout.CENTER);

        jPanelSelection.setLayout(new java.awt.BorderLayout());

        jCheckBoxEnableDisable.setToolTipText("Enable/Disable the selected tests");
        jCheckBoxEnableDisable.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jCheckBoxEnableDisable.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jCheckBoxEnableDisable.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBoxEnableDisableActionPerformed(evt);
            }
        });
        jPanelSelection.add(jCheckBoxEnableDisable, java.awt.BorderLayout.WEST);

        jButtonMoveDown.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/chevron-down-16-000000.png"))); // NOI18N
        jButtonMoveDown.setToolTipText("Move the selected tests down");
        jButtonMoveDown.setMaximumSize(new java.awt.Dimension(25, 25));
        jButtonMoveDown.setMinimumSize(new java.awt.Dimension(25, 25));
        jButtonMoveDown.setPreferredSize(new java.awt.Dimension(25, 25));
        jButtonMoveDown.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonMoveDownActionPerformed(evt);
            }
        });
        jPanelSelection.add(jButtonMoveDown, java.awt.BorderLayout.SOUTH);

        jButtonMoveUp.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/chevron-up-16-000000.png"))); // NOI18N
        jButtonMoveUp.setToolTipText("Move the selected tests up");
        jButtonMoveUp.setMaximumSize(new java.awt.Dimension(25, 25));
        jButtonMoveUp.setMinimumSize(new java.awt.Dimension(25, 25));
        jButtonMoveUp.setPreferredSize(new java.awt.Dimension(25, 25));
        jButtonMoveUp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonMoveUpActionPerformed(evt);
            }
        });
        jPanelSelection.add(jButtonMoveUp, java.awt.BorderLayout.PAGE_START);

        jPanelTopCommands.add(jPanelSelection, java.awt.BorderLayout.LINE_START);

        jPanel1.add(jPanelTopCommands, java.awt.BorderLayout.CENTER);

        add(jPanel1, java.awt.BorderLayout.NORTH);

        jTable1.setAutoCreateRowSorter(true);
        jTable1.setFont(new java.awt.Font("Tahoma", 0, 15)); // NOI18N
        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Enable", "Start Mode", "Time", "Device Name", "Device Description", "Test Suite", "Test Name",  "Test Peth", "Test Parameters", "Test Description", "Test Help", "Last Test Result", "Status", ""
            }
        ) {
            Class[] types = new Class [] {
                java.lang.Boolean.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class,  java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class, java.lang.Object.class,  java.lang.Object.class, java.lang.Object.class, javax.swing.Icon.class
            };
            boolean[] canEdit = new boolean [] {
                true, true, false, false, false, false, false, false, false,  false,  false, false, false, false
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit [columnIndex];
            }
        });
        jTable1.getTableHeader().setReorderingAllowed(false);
        jTable1.setDragEnabled(true);
        jTable1.setRowHeight(22);
        jTable1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jTable1MouseClicked(evt);
            }
        });
        jTable1.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                jTable1KeyReleased(evt);
            }
        });
        jScrollPane2.setViewportView(jTable1);

        add(jScrollPane2, java.awt.BorderLayout.CENTER);

        jPanelCustom.setPreferredSize(new java.awt.Dimension(300, 100));

        jPanelCustomHeader.setPreferredSize(new java.awt.Dimension(88, 20));
        jPanelCustomHeader.setLayout(new java.awt.BorderLayout());

        jButtonX.setFont(new java.awt.Font("Tahoma", 0, 8)); // NOI18N
        jButtonX.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/x-darker-8px.png"))); // NOI18N
        jButtonX.setToolTipText("Close this custom panel");
        jButtonX.setMaximumSize(new java.awt.Dimension(12, 12));
        jButtonX.setMinimumSize(new java.awt.Dimension(12, 12));
        jButtonX.setPreferredSize(new java.awt.Dimension(12, 12));
        jButtonX.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/x-darker-8px.png"))); // NOI18N
        jButtonX.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonXActionPerformed(evt);
            }
        });
        jPanelCustomHeader.add(jButtonX, java.awt.BorderLayout.EAST);

        jLabelCustomPanelName.setMaximumSize(new java.awt.Dimension(1000, 20));
        jLabelCustomPanelName.setMinimumSize(new java.awt.Dimension(100, 20));
        jLabelCustomPanelName.setPreferredSize(new java.awt.Dimension(100, 20));
        jLabelCustomPanelName.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jLabelCustomPanelNameMouseClicked(evt);
            }
        });
        jPanelCustomHeader.add(jLabelCustomPanelName, java.awt.BorderLayout.CENTER);

        javax.swing.GroupLayout jPanelCustomLayout = new javax.swing.GroupLayout(jPanelCustom);
        jPanelCustom.setLayout(jPanelCustomLayout);
        jPanelCustomLayout.setHorizontalGroup(
            jPanelCustomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelCustomLayout.createSequentialGroup()
                .addGroup(jPanelCustomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanelCustomHeader, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE)
                    .addComponent(jPanelCustomFrame, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        jPanelCustomLayout.setVerticalGroup(
            jPanelCustomLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanelCustomLayout.createSequentialGroup()
                .addComponent(jPanelCustomHeader, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanelCustomFrame, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        jPanelCustom.setVisible(false);

        add(jPanelCustom, java.awt.BorderLayout.SOUTH);
    }// </editor-fold>//GEN-END:initComponents

    private void jButtonRunActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonRunActionPerformed
        if (this.jButtonRun.getToolTipText().equals("Run enabled tests")) {
            Object pshellStatus;
            String sWait = "pshell is initialising: cannot start tests, please try again in a few seconds...";
            Boolean bWait = false;
            //check if pshell is still initialising: if so, don't start the tests
            try {
                //invoke python method
                pshellStatus = eval("get_context().state");
                if(String.valueOf(pshellStatus)=="Initializing"){
                    bWait = true;
                }               
            } catch (Exception ex) {
                Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
                bWait = true;
            } 
            //pshell is actually initialising: don't start tests
            if(bWait){
                System.out.println(sWait);
                return;                
            }
            setButtonToStart();
            updateStatus();
            executeTests();
        } else {
            try {
                setToStopped();
            } catch (InterruptedException ex) {
                Logger.getLogger(TestingList.class.getName()).log(Level.WARNING, null, ex);
            }
        }
    }//GEN-LAST:event_jButtonRunActionPerformed

    /**
     * reaction to Stop
     */
    private void setButtonToStop() throws InterruptedException {
        abort();
        this.jButtonRun.setToolTipText("Run enabled tests");
        jButtonRun.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/play-circled-64-000000.png")));
        this.jTable1.setEnabled(true);
        this.jButtonOpen.setEnabled(true);
        this.jButtonOptions.setEnabled(true);
        this.jButtonSave.setEnabled(true);
    }

    /**
     * reaction to Start
     */
    private void setButtonToStart() {
        this.jButtonRun.setToolTipText("Stop tests");
        jButtonRun.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/stop-circled-64-000000.png")));
        this.jTable1.clearSelection();
        this.jTable1.setEnabled(false);
        this.jButtonOpen.setEnabled(false);
        this.jButtonOptions.setEnabled(false);
        this.jButtonSave.setEnabled(false);
    }
    
    /**
     * enable/disable the tests in the table.
     * 
     * @param bEnable  if true, enable. Else, disable
     * @param onlyIfSelected if true, only the tests with Selected checkbox full will be enables/disabled. 
     *                      If false, all tests will be enabled/disabled
     * @param smartEnable if true, it enables/disables selected tests or, if none selected, it enables/disables all
     */
    private void enableAll(boolean bEnable, boolean onlyIfSelected, boolean smartEnable){
        int iSelRows = jTable1.getSelectedRowCount();

        if (onlyIfSelected  || (smartEnable && iSelRows > 0)) {// if some rows are selected, check/uncheck selected rows            
            for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
                for (int selRow = 0; selRow <= jTable1.getSelectedRowCount() - 1; selRow++) {
                    if (row == jTable1.getSelectedRows()[selRow]) {
                        jTable1.setValueAt(bEnable, row, 0);
                    }
                }
            }
        } else {//if none of the rows are selected, check/uncheck all
            for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
                jTable1.setValueAt(bEnable, row, 0);
            }
        }
        updateStatus();        
    }
    
    private void enableAll(boolean bEnable){
        enableAll(bEnable, false, false);       
    }
    
    private void enableSelection(boolean bEnable){
        enableAll(bEnable, true, true);       
    }

    private void jCheckBoxEnableDisableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxEnableDisableActionPerformed
        enableAll(jCheckBoxEnableDisable.isSelected(), false, true);
    }//GEN-LAST:event_jCheckBoxEnableDisableActionPerformed

    private void jTable1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jTable1MouseClicked
        if (this.jTable1.isEnabled()) {
            if(evt.getButton() == java.awt.event.MouseEvent.BUTTON3){
                //right mouse click
                //select row with right-click only if previously only one or zero rows were selected
                if(jTable1.getSelectedRowCount()<2){
                    Point cursor = evt.getPoint();
                    int rowAtPoint = jTable1.rowAtPoint(cursor);
                    jTable1.setRowSelectionInterval(rowAtPoint, rowAtPoint);
                }
                //enable jMenuItemShowDetails depending on user privileges
                this.jMenuItemShowDetails.setEnabled(Context.getInstance().getLevel()==AccessLevel.administrator);
                //enable action Show Test Details only if one row is selected
                this.jMenuItemShowDetails.setEnabled(jTable1.getSelectedRows().length == 1);                
                this.jMenuItemRunSingleTest.setEnabled(jTable1.getSelectedRows().length == 1);                
                jPopupMenuTable.show(jTable1, evt.getX(), evt.getY());
            }
            else{
                //left mouse click
                try {
                    switch (evt.getClickCount()) {
                        case 1:
                            break;
                        case 2:
                            openDetails();
                            break;
                    }
                    updateStatus();
                } catch (Exception ex) {
                    SwingUtils.showException(this, ex);
                }
            }
        }
    }//GEN-LAST:event_jTable1MouseClicked

    private void jTable1KeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTable1KeyReleased
        updateStatus();
    }//GEN-LAST:event_jTable1KeyReleased

    private void jButtonMoveDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonMoveDownActionPerformed
        moveDown();
    }//GEN-LAST:event_jButtonMoveDownActionPerformed

    private void jButtonMoveUpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonMoveUpActionPerformed
        moveUp();
    }//GEN-LAST:event_jButtonMoveUpActionPerformed

    private void jButtonOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOpenActionPerformed
        this.loadListFilter();
        //this below is a test that can be useful to check that multithreading does work fine with passing variables
        /*        new Thread(() -> {
         try {
         //Object ret = eval("parallelize((run,('testLocalVar1',globals(),{'a':'21', 'b':34})), (run,('testLocalVar2',globals(),{'a':231, 'b':234})))");
         Object ret;
         ret = eval("parallelize((run,('dummy',)))");
         ret = eval("parallelize((run,('testLocalVar1', None,{'a':21, 'b':34})), (run,('testLocalVar2', None, {'a':231, 'b':234})))");
         Object ret1 = ((ArrayList) ret).get(0);
         Object ret2 = ((ArrayList) ret).get(1);
         System.out.println(String.valueOf(ret1));
         System.out.println(String.valueOf(ret2));
         } catch (Exception ex) {
         SwingUtils.showException(this, ex);
         }
         }).start();
         */
    }//GEN-LAST:event_jButtonOpenActionPerformed

    private void jButtonSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonSaveActionPerformed
        saveSettings();
    }//GEN-LAST:event_jButtonSaveActionPerformed

    private void jButtonOptionsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOptionsActionPerformed
        //enable some menu items only for administrator access control level
        this.jMenuItemEditScript.setEnabled(Context.getInstance().getLevel()==AccessLevel.administrator);
        this.jMenuItemNewTest.setEnabled(Context.getInstance().getLevel()==AccessLevel.administrator);
        this.jMenuItemNewDevice.setEnabled(Context.getInstance().getLevel()==AccessLevel.administrator);
        jPopupMenuOptions.show(jButtonOptions, 0, jButtonOptions.getHeight());
    }//GEN-LAST:event_jButtonOptionsActionPerformed

    private void jCheckBoxMenuShowSelectedTestsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxMenuShowSelectedTestsActionPerformed
        // TODO add your handling code here:
        boolean bShow = jCheckBoxMenuShowSelectedTests.getState();
        showEnabledTestsOnly(bShow);
        jCheckBoxMenuShowSelectedTests1.setState(bShow);
    }//GEN-LAST:event_jCheckBoxMenuShowSelectedTestsActionPerformed

    private void jMenuItemOpenLogActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemOpenLogActionPerformed
        // TODO add your handling code here:
        String cmd;
        if (System.getProperty("os.name").toLowerCase().contains("windows")) {
            cmd = "notepad.exe " + TESTS_LOG_DEFAULT_DIR;
        } else {
            cmd = "nedit " + TESTS_LOG_DEFAULT_DIR;
        }
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException ex) {
            SwingUtils.showMessage(this, "jButtonOpenLogActionPerformed()", "jButtonOpenLogActionPerformed() " + ex.toString()
                    + " \nPlease open the file manually. \nFile dir: " + TESTS_LOG_DEFAULT_DIR);
            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
        }
    }//GEN-LAST:event_jMenuItemOpenLogActionPerformed

    private void jMenuItemReloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemReloadActionPerformed
        // TODO add your handling code here:
        reloadTests();
        try {
            cleanTests();
            cleanTests();
        } catch (IOException ex) {
            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
        }
    }//GEN-LAST:event_jMenuItemReloadActionPerformed

    private void jMenuItemNewTestActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewTestActionPerformed
        // TODO add your handling code here:
        openNewTestEditor("Test");
    }//GEN-LAST:event_jMenuItemNewTestActionPerformed

    private void jMenuItemNewDeviceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemNewDeviceActionPerformed
        // TODO add your handling code here:
        openNewTestEditor("Device");
    }//GEN-LAST:event_jMenuItemNewDeviceActionPerformed

    private void jCheckBoxMenuShowSelectedTests1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBoxMenuShowSelectedTests1ActionPerformed
        // TODO add your handling code here:
        boolean bShow = jCheckBoxMenuShowSelectedTests1.getState();
        showEnabledTestsOnly(bShow);
        jCheckBoxMenuShowSelectedTests.setState(bShow);        
    }//GEN-LAST:event_jCheckBoxMenuShowSelectedTests1ActionPerformed

    private void jMenuItemReload1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemReload1ActionPerformed
        // TODO add your handling code here:
        reloadTests();
        try {
            cleanTests();
            cleanTests();
        } catch (IOException ex) {
            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
        }        
    }//GEN-LAST:event_jMenuItemReload1ActionPerformed

    private void jMenuItemShowDetailsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemShowDetailsActionPerformed
        try {
            // TODO add your handling code here:
            openDetails();
        } catch (Exception ex) {
            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
        }
    }//GEN-LAST:event_jMenuItemShowDetailsActionPerformed

    private void jMenuItemSelectAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemSelectAllActionPerformed
        // TODO add your handling code here:
        enableAll(true);
    }//GEN-LAST:event_jMenuItemSelectAllActionPerformed

    private void jMenuItemSelectNoneActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemSelectNoneActionPerformed
        // TODO add your handling code here:
        enableAll(false);
    }//GEN-LAST:event_jMenuItemSelectNoneActionPerformed

    private void jMenuItemSelectSelectionActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemSelectSelectionActionPerformed
        // TODO add your handling code here:
        enableSelection(true);
    }//GEN-LAST:event_jMenuItemSelectSelectionActionPerformed

    private void jMenuItemDeselectSelectionActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemDeselectSelectionActionPerformed
        // TODO add your handling code here:
        enableSelection(false);
    }//GEN-LAST:event_jMenuItemDeselectSelectionActionPerformed

    private void jMenuItemEditScriptActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemEditScriptActionPerformed
        // TODO add your handling code here:
        int rows[] = jTable1.getSelectedRows();
        for(int row=0 ; row<rows.length ; row++){
            try {
                String sTestPath = String.valueOf(jTable1.getValueAt(rows[row], COL.TESTPATH.ordinal()));
                System.out.println(sTestPath);
                ((View)App.getInstance().getMainFrame()).openScript(sTestPath);
            } catch (IOException ex) {
                Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }//GEN-LAST:event_jMenuItemEditScriptActionPerformed

    private void jMenuItemRunSingleTestActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemRunSingleTestActionPerformed
        // TODO add your handling code here:
        enableAll(false);
        enableSelection(true);
        jButtonRunActionPerformed( evt);
    }//GEN-LAST:event_jMenuItemRunSingleTestActionPerformed

    private void jMenuLoadCustomPanelMenuSelected(javax.swing.event.MenuEvent evt) {//GEN-FIRST:event_jMenuLoadCustomPanelMenuSelected
        // TODO add your handling code here:
        List<String>  menuList = getCustomPanels();
        jMenuLoadCustomPanel.removeAll();
        for(int u = 0; u < menuList.size(); u++){
                
                JMenuItem menuItem = new JMenuItem(menuList.get(u));
                menuItem.addActionListener(new java.awt.event.ActionListener() {
                    public void actionPerformed(java.awt.event.ActionEvent evt) {
                        loadCustomPanel(((JMenuItem)evt.getSource()).getText());
                    }
                });                
                jMenuLoadCustomPanel.add(menuItem);
        }
    }//GEN-LAST:event_jMenuLoadCustomPanelMenuSelected

    private void jButtonXActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonXActionPerformed

        closeCustomPanel();
    }//GEN-LAST:event_jButtonXActionPerformed

    private void jLabelCustomPanelNameMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jLabelCustomPanelNameMouseClicked

            if(evt.getButton() == java.awt.event.MouseEvent.BUTTON3){
                //right mouse click              
                jPopupMenuCustom.show(jLabelCustomPanelName, evt.getX(), evt.getY());
            }      
    }//GEN-LAST:event_jLabelCustomPanelNameMouseClicked

    private void jMenuItemCustomCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemCustomCloseActionPerformed
        this.closeCustomPanel();
    }//GEN-LAST:event_jMenuItemCustomCloseActionPerformed

    private void jMenuItemDeselectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemDeselectActionPerformed
        // TODO add your handling code here:
        this.jTable1.clearSelection();
    }//GEN-LAST:event_jMenuItemDeselectActionPerformed

    //<editor-fold defaultstate="collapsed" desc="Witget Variables declaration"> 
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButtonMoveDown;
    private javax.swing.JButton jButtonMoveUp;
    private javax.swing.JButton jButtonOpen;
    private javax.swing.JButton jButtonOptions;
    private javax.swing.JButton jButtonRun;
    private javax.swing.JButton jButtonSave;
    private javax.swing.JButton jButtonX;
    private javax.swing.JCheckBox jCheckBoxEnableDisable;
    private javax.swing.JCheckBoxMenuItem jCheckBoxMenuShowSelectedTests;
    private javax.swing.JCheckBoxMenuItem jCheckBoxMenuShowSelectedTests1;
    private javax.swing.JLabel jLabelCustomPanelName;
    private javax.swing.JMenu jMenuAdvanced;
    private javax.swing.JMenuItem jMenuItemCustomClose;
    private javax.swing.JMenuItem jMenuItemDeselect;
    private javax.swing.JMenuItem jMenuItemDeselectSelection;
    private javax.swing.JMenuItem jMenuItemEditScript;
    private javax.swing.JMenuItem jMenuItemNewDevice;
    private javax.swing.JMenuItem jMenuItemNewTest;
    private javax.swing.JMenuItem jMenuItemOpenLog;
    private javax.swing.JMenuItem jMenuItemReload;
    private javax.swing.JMenuItem jMenuItemReload1;
    private javax.swing.JMenuItem jMenuItemRunSingleTest;
    private javax.swing.JMenuItem jMenuItemSelectAll;
    private javax.swing.JMenuItem jMenuItemSelectNone;
    private javax.swing.JMenuItem jMenuItemSelectSelection;
    private javax.swing.JMenuItem jMenuItemShowDetails;
    private javax.swing.JMenu jMenuLoadCustomPanel;
    private javax.swing.JMenu jMenuSelect;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanelButtons;
    private javax.swing.JPanel jPanelCustom;
    private javax.swing.JPanel jPanelCustomFrame;
    private javax.swing.JPanel jPanelCustomHeader;
    private javax.swing.JPanel jPanelSelection;
    private javax.swing.JPanel jPanelTopCommands;
    private javax.swing.JPopupMenu jPopupMenuConfigs;
    private javax.swing.JPopupMenu jPopupMenuCustom;
    private javax.swing.JPopupMenu jPopupMenuOptions;
    private javax.swing.JPopupMenu jPopupMenuTable;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    private javax.swing.JPopupMenu.Separator jSeparator2;
    private javax.swing.JPopupMenu.Separator jSeparator3;
    private javax.swing.JPopupMenu.Separator jSeparator4;
    private javax.swing.JPopupMenu.Separator jSeparator5;
    private javax.swing.JPopupMenu.Separator jSeparator6;
    private javax.swing.JPopupMenu.Separator jSeparator7;
    private javax.swing.JTable jTable1;
    // End of variables declaration//GEN-END:variables
    //</editor-fold> 

    /**
     * reload tests from files
     */
    public void reloadTests() {
        try {
            loadTests();
        } catch (IOException ex) {
            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * filter table: show only rows selected (checkbox checked) for running.
     *
     * @param show if false, show all tests. If true, show only selected tests.
     */
    public void showEnabledTestsOnly(boolean show) {
        String filter;
        if (show) {
            filter ="true";
        } else {
            filter ="";
        }
        filterTests(filter);
        //store filter in the properties  
        saveProperties("showEnabledTestsOnly", filter);
        //if only enabled tests are visible, disable the move buttons
        this.jButtonMoveUp.setEnabled(!show);
        this.jButtonMoveDown.setEnabled(!show);
        //check related checkboxes
        jCheckBoxMenuShowSelectedTests.setSelected(show); 
        jCheckBoxMenuShowSelectedTests1.setSelected(show);
    }
    
    /**
     * open New Test/Device editor panel
     * 
     * @param type can be "Device" or "Test". Either device or test will be created.
     */
    public void openNewTestEditor(String type){
        try {
            JDialog dlg = new JDialog(getView(), "New "+ type, true);
            //create a class to visualise the details panel
            Class testingListDetailsClass = getContext().getClassByName("NewTest");
            JPanel detailsPanel = (JPanel) testingListDetailsClass.getConstructor(new Class[]{String.class}).newInstance(new Object[]{type});            
            dlg.getContentPane().add(detailsPanel);
            dlg.pack();
            dlg.setVisible(true);
        } catch (Exception ex) {
            System.out.println("animateCustomPanel(): "+String.valueOf(ex));
        }
    }

    /**
     * filter table: show only rows containing the specified text.
     *
     * @param filterText text for filtering; can be a regex pattern.
     */
    private void filterTests(String filterText) {
        TableModel model = new DefaultTableModel() {
            public Class getColumnClass(int column) {
                Class returnValue;
                if ((column >= 0) && (column < getColumnCount())) {
                    returnValue = getValueAt(0, column).getClass();
                } else {
                    returnValue = Object.class;
                }
                return returnValue;
            }
        };
        model = jTable1.getModel();

        final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(model);
        jTable1.setRowSorter(sorter);
        if (filterText.length() == 0) {
            sorter.setRowFilter(null);
        } else {
            try {
                sorter.setRowFilter(RowFilter.regexFilter(filterText));
            } catch (PatternSyntaxException pse) {
                System.err.println("Bad regex pattern");
            }
        }
    }

    /**
     * return whether or not the tests can run (Run launched by the user)
     *
     * @return status of run button. True = tests launching sequence is running
     */
    public boolean isTestRunAllowed() {
        return (this.jButtonRun.getToolTipText().equals("Stop tests") && pendingTestsCount() > 0);
    }

    /**
     * move selected rows up in table
     * 
     */
    private void moveUp() {
        DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
        int[] rows = jTable1.getSelectedRows();
        try{
                if (rows[0] - 1 >= 0) {
                jTable1.setAutoCreateRowSorter(false);
                model.moveRow(rows[0], rows[rows.length - 1], rows[0] - 1);
                jTable1.setAutoCreateRowSorter(true);
                jTable1.setRowSelectionInterval(rows[0] - 1, rows[rows.length - 1] - 1);
                }
        }
        catch(Exception ex){
            //fail silently
        }        
    }

    /**
     * move selected rows down in table
     */
    private void moveDown() {
        DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
        int[] rows = jTable1.getSelectedRows();
        try{
            if (rows[rows.length - 1] < jTable1.getRowCount()) {
                jTable1.setAutoCreateRowSorter(false);
                model.moveRow(rows[0], rows[rows.length - 1], rows[0] + 1);
                jTable1.setAutoCreateRowSorter(true);
                jTable1.setRowSelectionInterval(rows[0] + 1, rows[rows.length - 1] + 1);
            }
        }
        catch(Exception ex){
            //fail silently
        }
    }

    /**
     * open details of the selected row in a new panel
     *
     * @throws exception
     */
    private void openDetails() throws Exception {
        //pick details from the clicked row
        int row = jTable1.getSelectedRow();
        String sDeviceName = jTable1.getValueAt(row, COL.DEVICENAME.ordinal()).toString();
        String sTestName = jTable1.getValueAt(row, COL.TESTNAME.ordinal()).toString();
        String sTestPath = String.valueOf(jTable1.getValueAt(row, COL.TESTPATH.ordinal()));
        String sTestCaseName = jTable1.getValueAt(row, COL.TESTSUITE.ordinal()).toString();
        String sTestDescription = getTestDescription(sTestPath);
        String sDeviceDescription = jTable1.getValueAt(row, COL.DEVICEDESCR.ordinal()).toString();
        String sLastResult = jTable1.getValueAt(row, COL.RESULT.ordinal()).toString();
        String sResultTime = jTable1.getValueAt(row, COL.TIME.ordinal()).toString();
        String sTestHelp = String.valueOf(jTable1.getValueAt(row, COL.TESTHELP.ordinal()));
        HashMap mParameters = getTestParameters(sTestPath);
        //create map for passing details to Details Panel
        HashMap details = new HashMap();
        details.put("deviceName", sDeviceName);
        details.put("deviceDescription", sDeviceDescription);
        details.put("testDescription", sTestDescription);
        details.put("testSuite", sTestCaseName);
        details.put("testName", sTestName);
        details.put("testResult", sLastResult);
        details.put("time", sResultTime);
        details.put("parameters", mParameters);
        details.put("testHelp", sTestHelp);
        details.put("testPath", sTestPath);
        //open details panel
        JDialog dlg = new JDialog(getView(), "Test Details - " + sTestName, true);
        //create a class to visualise the details panel
        Class testingListDetailsClass = getContext().getClassByName("TestingListDetails");
        JPanel detailsPanel = (JPanel) testingListDetailsClass.getConstructor(new Class[]{HashMap.class}).newInstance(new Object[]{details});

        dlg.getContentPane().add(detailsPanel);
        //dlg.add(new TestingListDetails());
        dlg.pack();
        dlg.setVisible(true);
    }

    /**
     * open details of the selected row in a new panel
     *
     * @throws exception
     */
    private void openListEditor() throws Exception {

        JDialog dlg = new JDialog(getView(), "Add/remove tests", true);
        //create a class to visualise the edit panel
        Class editTestingListClass = getContext().getClassByName("EditTestingList");
        JPanel detailsPanel = (JPanel) editTestingListClass.getConstructor(new Class[]{TableModel.class}).newInstance(jTable1.getModel());

        dlg.getContentPane().add(detailsPanel);
        //dlg.add(new TestingListDetails());
        dlg.pack();
        dlg.setVisible(true);
    }

    
    /**
     * put the test result in table
     *
     * @param deviceName name of the device to search in table
     * @param testPath path of the test to search in table
     * @param res String result returned by the test
     * @param status String status returned by the test
     * @return int index of the row of the device in table
     */
    public int showResult(String deviceName, String testPath, String res, String status) throws InterruptedException {
        int rowD = -1;
        testPath = testPath.replace("\\\\", "\\");
        String sTestName = testPath;
        //getContext().getDataManager().appendLog(str(log));

        //System.out.println("Looking for: deviceName: " + deviceName + "; testPath: " + testPath + " in table.");
        
        String sStatus;
        if (status == "true") {
            sStatus = TestStatus.SUCCESS.toString();
        } else if (status == "false") {
            sStatus = TestStatus.FAILURE.toString();
        } else {
            sStatus = status;
        }
        //search for device name in table
        for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
            if (deviceName.equals(jTable1.getValueAt(row, COL.DEVICENAME.ordinal()))
                    && testPath.equals(jTable1.getValueAt(row, COL.TESTPATH.ordinal()))) {
                rowD = row;
                sTestName = jTable1.getValueAt(rowD, COL.TESTNAME.ordinal()).toString();
                break;
            }
        }
        ImageIcon icon = new ImageIcon();
        switch (sStatus) {
            case "Success":
                icon = TestStatus.SUCCESS.Icon();
                logger.log(Level.INFO, sStatus + " - Device: " + deviceName + "; Test: " + sTestName + "; Result: " + res);
                log("INFO - " + sStatus + " - Device: " + deviceName + "; Test: " + sTestName + "; Result: " + res);
                break;
            case "Failure":
                icon = TestStatus.FAILURE.Icon();
                logger.log(Level.SEVERE, sStatus + " - Device: " + deviceName + "; Test: " + sTestName + "; Result: " + res);
                break;
            case "Running":
                icon = TestStatus.RUNNING.Icon();
                //logger.log(Level.INFO, "Running Test: " + sTestName + ".");
                break;
        }
        if (rowD >= 0) {
            jTable1.setValueAt(icon, rowD, COL.ICON.ordinal());
            jTable1.setValueAt(getNow(), rowD, COL.TIME.ordinal());
            jTable1.setValueAt(res, rowD, COL.RESULT.ordinal());
            jTable1.setValueAt(sStatus, rowD, COL.STATUS.ordinal());
        } else {
            logger.log(Level.SEVERE, "Cant find Test: " + testPath + " in table.");
        }
        //check if there are still pending tests. If not, set the status of the tool to Stopped.
        if (pendingTestsCount() == 0 && runningTestsCount() == 0) {
            setToStopped();
        }
        return rowD;
    }

    /**
     * return the amount of tests currently in Pending state
     *
     * @return int counter of pending tests
     */
    public int pendingTestsCount() {
        return testsStatusCount(TestStatus.PENDING);
    }

    /**
     * return the amount of tests currently in Running state
     *
     * @return int counter of running tests
     */
    public int runningTestsCount() {
        return testsStatusCount(TestStatus.RUNNING);
    }

    /**
     * return the amount of tests currently in Success state
     *
     * @return int counter of successful tests
     */
    public int successTestsCount() {
        return testsStatusCount(TestStatus.SUCCESS);
    }

    /**
     * returns the amount of tests currently in "status" state
     *
     * @param status type of status to count
     * @return int counter with occurrences of the status
     */
    private int testsStatusCount(TestStatus status) {
        String sStatus;
        boolean bSelected;
        int iPendingTestsCount = 0;
        for (int row = 0; row < jTable1.getRowCount(); row++) {
            bSelected = (boolean) jTable1.getValueAt(row, COL.CHECK.ordinal());
            sStatus = jTable1.getValueAt(row, COL.STATUS.ordinal()).toString();
            if (bSelected && sStatus == status.toString()) {
                iPendingTestsCount++;
            }
        }
        return iPendingTestsCount;
    }

    /**
     * set the tests to stopped
     */
    private void setToStopped() throws InterruptedException {
        //No more tests to play. Stop 
        setButtonToStop();
        logger.log(Level.INFO, "End of tests.");
    }

    /**
     * get the first test currently in progress
     *
     * @return properties of the test in progress: {deviceName,testPath}
     */
    public String[] getTestInProgress() {
        String[] dsTestProperties = {"", ""};
        //search for device name in table
        try {
            for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
                if (jTable1.getValueAt(row, COL.STATUS.ordinal()).toString().equals(TestStatus.RUNNING.toString())) {
                    dsTestProperties[0] = jTable1.getValueAt(row, COL.DEVICENAME.ordinal()).toString();
                    dsTestProperties[1] = jTable1.getValueAt(row, COL.TESTPATH.ordinal()).toString();
                    break;
                }
            }
        } catch (Exception ex) {
            this.setToStopped();
            SwingUtils.showMessage(this, "getTestInProgress()", ex.toString());
        } finally {
            return dsTestProperties;
        }
    }
    
    /**
     * Stop all the tests in progress and return to idle state (no further tests are launched)
     * 
     * @param resultMessage the message that will be shown as Result of each stopped test
     */
    public void stopAll(String resultMessage) throws InterruptedException{ 
        setToStopped();
        String testInProgress[] = getTestInProgress();
        while(testInProgress[0]!=""){
            testInProgress = getTestInProgress();
            showResult(testInProgress[0], testInProgress[1], resultMessage, "false");
        }
        try {
            Object ret = eval("log('Tests interrupted by user')");
        } catch (Exception ex) {
            System.out.println("animateCustomPanel(): "+String.valueOf(ex));
        }
    }

    /**
     * return the status of the specified test name for specified device name
     *
     * @param deviceName name of the device
     * @param testPath path of the test file
     * @return String containing the result returned by the test
     */
    public String getResult(String deviceName, String testPath) {
        String sStatus = "";
        int rowD = -1, colT = -1;
        //search for device name in table
        for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
            if (deviceName.equals(jTable1.getValueAt(row, COL.DEVICENAME.ordinal()))) {
                rowD = row;
                break;
            }
        }
        if (rowD >= 0) {
            //search for test in table
            if (testPath.equals(jTable1.getValueAt(rowD, COL.TESTPATH.ordinal()))) {
                colT = COL.TESTNAME.ordinal();
            }
        }
        //get the test result
        if (colT >= 0 && rowD >= 0) {
            sStatus = jTable1.getValueAt(rowD, COL.STATUS.ordinal()).toString();
        }
        return sStatus;
    }

    /**
     * visualise test status (columns status and icon)
     */
    public void updateStatus() {
        String sStatus;
        boolean bSelected;
        ImageIcon icon = null;
        String sStart;
        for (int row = 0; row <= jTable1.getRowCount() - 1; row++) {
            bSelected = (boolean) jTable1.getValueAt(row, COL.CHECK.ordinal());
            sStart = String.valueOf(jTable1.getValueAt(row, COL.STARTSEQUENCE.ordinal()));
            if (bSelected) {
                sStatus = TestStatus.PENDING.toString();
                icon = TestStatus.PENDING.Icon();
                if (sStart == "") {
                    sStart = StartSequence.AFTER.toString();
                }
            } else {
                sStatus = TestStatus.DISABLED.toString();
                icon = TestStatus.DISABLED.Icon();
                sStart = "";
            }
            jTable1.setValueAt(bSelected, row, COL.CHECK.ordinal());
            jTable1.setValueAt(icon, row, COL.ICON.ordinal());
            jTable1.setValueAt(sStatus, row, COL.STATUS.ordinal());
            jTable1.setValueAt(sStart, row, COL.STARTSEQUENCE.ordinal());
        }
    }

    /**
     * launch the execution of the tests
     */
    public void executeTests() {
        RunTest runTest = new RunTest();
        Thread t = new Thread(runTest);
        t.start();
    }

    /**
     *  tests run multithreaded
     */
    public class RunTest implements Runnable {

        private HashMap mParameters;
        //private HashMap<String,Object> args;
        private HashMap<String, HashMap> hTests ;

        /**
         *
         */
        public RunTest() {
            //System.out.println("A0");
        }

        public void run() {
            // code in the other thread, can reference "var" variable
            loadCustomPanel(getCustomPanel());
            try {
                executeTest(0);
            } catch (InterruptedException ex) {
                Logger.getLogger(TestingList.class.getName()).log(Level.WARNING, null, ex);
            }
        }

        /**
         * execute the selected tests in the list, starting from the position
         *
         * @param position row number from which to start executing tests
         */
        private void executeTest(int position) throws InterruptedException {
            boolean bSelected = false;
            String sStartSequence, sStatus;
            int[] selectedTestsRows = {};
            //scan through the table starting from 'position' and execute the first selected test found
            int row = 0;// position;
            if (row < jTable1.getRowCount()) {
                for (row = position; row < jTable1.getRowCount(); row++) {
                    bSelected = (boolean) jTable1.getValueAt(row, COL.CHECK.ordinal());
                    sStartSequence = jTable1.getValueAt(row, COL.STARTSEQUENCE.ordinal()).toString();
                    sStatus = jTable1.getValueAt(row, COL.STATUS.ordinal()).toString();
                    //collect tests to be launched in parallel
                    //the test must be: selected, set as start with previous, pending.
                    //alternatively, the test must be: selected, first of the list.
                    //System.out.println(String.valueOf(row) + "\t" + String.valueOf(bSelected) + "\t" + String.valueOf(selectedTestsRows.length) + "\t" + sStartSequence + "\t" + sStatus);
                    if (bSelected
                            && sStatus.equals(TestStatus.PENDING.toString())
                            && (selectedTestsRows.length == 0 || //the test must be: selected, pending, first of the list.
                            sStartSequence.equals(StartSequence.TOGETHER.toString()))) { //or the test must be: selected, pending, set as start with previous
                        selectedTestsRows = append(selectedTestsRows, row);
                    } else if (bSelected
                            && sStatus.equals(TestStatus.PENDING.toString()) &&//if this test must be executed...
                            selectedTestsRows.length > 0 && //but there are already tests to be executed in parallel....
                            (sStartSequence.equals(StartSequence.AFTER.toString()))) { //...and this test must be executed in series, then stop searching
                        break;
                    }
                }
                //if the user pressed Stop, or error, exit from run:
                if(!isTestRunAllowed())
                    return;
                if (selectedTestsRows.length > 0) { //at least one test is selected: launch it (or them)
                    System.out.println("\n===================================================\n");
                    executeParallelTestsGroup(selectedTestsRows); //last execution did not find a test file. Continue with next execution
                    executeTest(position + 1);
                } else {//no tests were selected. end.
                    setToStopped();
                }
            }
        }

        /**
         * build a HashMap containing the items picked from a specific table row.
         * 
         * @param row the row whose items will fill the HashMap
         * @return the HashMap containing the items picked from the specified table row.
         */
        private HashMap buildMapFromTableRow(int row) {
            String sDeviceName = jTable1.getValueAt(row, COL.DEVICENAME.ordinal()).toString();
            String sTestName = jTable1.getValueAt(row, COL.TESTNAME.ordinal()).toString();
            String sTestCaseName = jTable1.getValueAt(row, COL.TESTSUITE.ordinal()).toString();
            String sTestPath = jTable1.getValueAt(row, COL.TESTPATH.ordinal()).toString();
//            HashMap mParameters = buildParametersMap(String.valueOf(jTable1.getValueAt(row, COL.TESTPARAMS.ordinal())));
            //get the parameters directly frm he config file
            HashMap mParameters = getTestParameters(sTestPath);
            HashMap testArgs = new HashMap();
            //args.put("ret", "");
            testArgs.put(testArgNames.PARAMETERS.toString(), mParameters);
            testArgs.put(testArgNames.TEST.toString(), sTestName);
            testArgs.put(testArgNames.DEVICE.toString(), sDeviceName);
            testArgs.put(testArgNames.TEST_PATH.toString(), sTestPath);
            testArgs.put(testArgNames.STATUS.toString(), false);

            return testArgs;
        }     

        /**
         * convert HashMap string separators to python dictionary string
         * separators
         *
         * @param sParameters string coming from HashMap conaining the list of
         * parameters to be passed to the test script. Example:
         * {repeatTimes={description=Repeat N times, value=1},
         * midPoint={description=Middle point A, value=41.0},
         * spanFromMidPoint={description=B steps around middle point A,
         * value=2.0}}
         * @return string of dictionary in python syntax. This can be passed to
         * the python test script. Example of output:
         * {'repeatTimes':{'description':'Repeat N
         * times','value':'1'},'midPoint':{'description':'Middle point
         * A','value':'41.0'},'spanFromMidPoint':{'description':'B steps around
         * middle point A','value':'2.0'}
         */
        private String convertParametersMapToPythonMap(String sParameters) {
            String sPythonParams = sParameters;
            sPythonParams = sPythonParams.replace(", ", ",");
            sPythonParams = sPythonParams.replace(" ,", ",");
            sPythonParams = sPythonParams.replace("{{", "#@");
            sPythonParams = sPythonParams.replace("}}", "@#");
            sPythonParams = sPythonParams.replace("},", "@%#");
            sPythonParams = sPythonParams.replace(",{", "#%@");
            sPythonParams = sPythonParams.replace("={", "^%&");
            sPythonParams = sPythonParams.replace("=}", "&%^");
            sPythonParams = sPythonParams.replace(",", "','");
            sPythonParams = sPythonParams.replace("{", "{'");
            sPythonParams = sPythonParams.replace("}", "'}");
            sPythonParams = sPythonParams.replace("=", "':'");
            sPythonParams = sPythonParams.replace("^%&", "':{'");
            sPythonParams = sPythonParams.replace("&%^", "'}:'");
            sPythonParams = sPythonParams.replace("#@", "{{'");
            sPythonParams = sPythonParams.replace("@#", "'}}");
            sPythonParams = sPythonParams.replace("@%#", "'},'");
            sPythonParams = sPythonParams.replace("#%@", "',{'");
            return sPythonParams;
        }

        /**
         * start all the tests in the array rowsToExecute. all tests in the
         * listed row indexes will be started at the same time.
         *
         * @param rowsToExecute array of indexes relative to the test rows in
         * the table. The indexes in the array will execute the test in
         * corresponding row
         * @return return value: 0 means ok, <0 means problem
         */
        private int executeParallelTestsGroup(int[] rowsToExecute) throws InterruptedException {
System.out.println(Convert.arrayToString(rowsToExecute, " - " , 10));
            hTests = new HashMap();
            int iRet = -1;
            HashMap args2 = new HashMap(); //this is the global map that will contain one map per test.
            HashMap testArgs; //this is the map for a test.
            RunTest runTest;
            String sTestName = "", sDeviceName = "", sTestPath = "", sTestID = ""; //sTestID is the compination of device name + test path
            for (int row : rowsToExecute) {
                //System.out.println(String.valueOf(row) + "\t" + sDeviceName + "\t" + sTestName + "\t" + sTestCaseName + "\t" + String.valueOf(rowsToExecute.length));
                try {
                    testArgs = buildMapFromTableRow(row);
                    sTestName = (String) testArgs.get(testArgNames.TEST.toString());
                    sTestPath = (String) testArgs.get(testArgNames.TEST_PATH.toString());
                    sDeviceName = (String) testArgs.get(testArgNames.DEVICE.toString());
                    mParameters = (HashMap) testArgs.get(testArgNames.PARAMETERS.toString());
                    sTestID = sDeviceName+"|"+sTestPath;
                    File f = new File(sTestPath);
System.out.println("Path = " + sTestPath);
                    if (!f.exists() || f.isDirectory()) {
                        logger.log(Level.SEVERE, "Cannot find test script: " + sTestPath);
                        showResult(sDeviceName, sTestPath, "Cannot find test script: " + sTestPath, TestStatus.FAILURE.toString());
                        hTests = args2;
                        continue;
                    }
                    //if the user pressed Stop, exit from run:
                    if(!isTestRunAllowed())
                        return iRet;
                    showResult(sDeviceName, sTestPath, "Test running", TestStatus.RUNNING.toString());
                    //launch the test
                    if (!mParameters.isEmpty()) {
                        logger.log(Level.INFO, "Running test '" + sTestName + "' with the following parameters: " + mParameters.toString());
                        System.out.println("Running test '" + sTestName + "' with the following parameters: " + mParameters.toString());
                    } else {
                        logger.log(Level.INFO, "Running Test '" + sTestName + "'. No parameters found.");
                        System.out.println("Running test '" + sTestName + "'. No parameters found.");
                    }
                    args2.put(sTestID, testArgs);
                    hTests = args2;
                    iRet = 0;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    SwingUtils.showMessage(TestingList.this, "executeTest()", ex.toString());
                    logger.log(Level.SEVERE, ex.toString());
                    showResult(sDeviceName, sTestPath, ex.toString(), TestStatus.FAILURE.toString());
                    setToStopped();
                }
            }
System.out.println(hTests);
            try {
                int iLastExecutedTestIndex = -1;
                final String sParallelizeBegin = "(run,(str('";
                final String sParallelizeEnd = "'),))";
                String sParallelizeArguments;
                try {
                    //System.out.println("A0.1");                   
                    String sParallelizeCommand = "parallelize(";
                    int i = 0;
                    int iTotalEntries = hTests.entrySet().size();
                    HashMap args = new HashMap();
                    //System.out.println("hTests: "+hTests.entrySet().toString());
                    if(hTests.isEmpty()) return iRet;
                    for (Map.Entry<String, HashMap> hTest : hTests.entrySet()) {
                        sTestPath = (String) hTest.getValue().get(testArgNames.TEST_PATH.toString());
                        sTestPath = sTestPath.replace("\\", "\\\\");
                        sParallelizeCommand = sParallelizeCommand + "(run,(str('" + sTestPath;
                        sParallelizeArguments = "None, {'parameters':" + convertParametersMapToPythonMap(hTest.getValue().get("parameters").toString())
                                + ",'test':'" + hTest.getValue().get(testArgNames.TEST.toString())
                                + "','device':'" + hTest.getValue().get(testArgNames.DEVICE.toString())
                                + "','status':'" + hTest.getValue().get(testArgNames.STATUS.toString()) + "'}";
                        sParallelizeCommand = sParallelizeCommand + "')," + sParallelizeArguments;
                        i++;
                        if (i < iTotalEntries) {
                            sParallelizeCommand = sParallelizeCommand + ")),"; //between each "run" command
                        } else {
                            sParallelizeCommand = sParallelizeCommand + ")))"; //very last part of command "parallelize"
                        }
                    }
                    //animate the custom panel (if present)
                    animateCustomPanel(sDeviceName);
                    //System.out.println(sParallelizeCommand);
                    //run test(s)
                    Object ret = eval(sParallelizeCommand);
                    //System.out.println(ret);
                    String sTestResult, sTestStatus;
                    if(ret == null){ 
                            SwingUtils.showMessage(TestingList.this, "executeParallelTestsGroup()", "The test script(s) did not return any feedback.");
                            System.out.println("The test script(s) did not return any feedback.");                        
                    }
                    //scan through all return mappings of all run tests
                    for (Object oTestRet : (ArrayList) ret) {
                        //check if the mapping of the selected test has all expected return values
                        if (((List<PyList>) oTestRet).size() >= 4) {
                            //extract return data of the current test
                            sTestPath   = String.valueOf(((List<PyList>) oTestRet).get(0));
                            sDeviceName = String.valueOf(((List<PyList>) oTestRet).get(1));
                            sTestResult = String.valueOf(((List<PyList>) oTestRet).get(2));
                            sTestStatus = String.valueOf(((List<PyList>) oTestRet).get(3));
                            sTestID = sDeviceName+"|"+sTestPath;
                            //from the map of executed tests, get the test name (key is test path)
                            HashMap<String, HashMap> hTest = hTests.get(sTestID);
                            sTestName = String.valueOf(hTest.get(testArgNames.TEST.toString()));
                        } else { //problem, the test script does not return all the expected return values
                            String sErrorText = "Test " + sTestPath + " did not return all required return values";
                            Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, sErrorText);
                            SwingUtils.showMessage(TestingList.this, "executeParallelTestsGroup()", sErrorText);
                            System.out.println(String.valueOf(sErrorText));
                            //try to fetch at least first element of returned map. If fails, it will go to catch
                            sTestID = String.valueOf(((List<PyList>) oTestRet).get(0));
                            sTestStatus = sErrorText;
                            sTestResult = "false";
                            HashMap<String, HashMap> hTest = hTests.get(sTestID);
                            sDeviceName = String.valueOf(hTest.get(testArgNames.DEVICE.toString()));
                            sTestName = String.valueOf(hTest.get(testArgNames.TEST.toString()));
                        }
                        //System.out.println("Result: " + sDeviceName + "|" + sTestPath + "|" + sTestResult + "|" + sTestStatus);
                        iLastExecutedTestIndex = showResult(sDeviceName, sTestPath, sTestResult, sTestStatus);
                    }
                }  catch (StatementException intEx) {
                    intEx.printStackTrace();
                    Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, intEx);
                    System.out.println("User interrupted test! |"+ sDeviceName+"|"+ sTestPath+"|"+ String.valueOf(intEx));
                    stopAll("Test stopped by user");
                    //showResult(sDeviceName, sTestPath, "User interrupted test", "false");
                    //setToStopped();
                } catch (ClassCastException ccex) {
                    ccex.printStackTrace();
                    Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ccex);
                    SwingUtils.showMessage(TestingList.this, "executeParallelTestsGroup()", ccex.toString());
                    //System.out.println(String.valueOf(ccex));
                    if (!sDeviceName.isEmpty()) {
                        showResult(sDeviceName, sTestPath, ccex.toString(), TestStatus.FAILURE.toString());
                    }
                }catch (Exception ex) {
                    ex.printStackTrace();
                    Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
                    SwingUtils.showMessage(TestingList.this, "executeParallelTestsGroup()", ex.toString());
                    System.out.println(String.valueOf(ex));
                    showResult(sDeviceName, sTestPath, ex.toString(), TestStatus.FAILURE.toString());
                } 
            } catch (Exception ex) {
                ex.printStackTrace();
                SwingUtils.showMessage(TestingList.this, "executeParallelTestsGroup(), run thread", ex.toString());
                logger.log(Level.SEVERE, ex.toString());
                setToStopped();
            } 
            return iRet;
        }
    }

    /**
     * table management (hide internal columns, size, etc)
     */
    private void buildTable() {
        String sDate = getNow();
        DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
        jTable1.setModel(model);

        //Set up the combo box editor for the Start Sequence cells.
        JComboBox comboBox = new JComboBox();
        comboBox.addItem(StartSequence.AFTER.toString());
        comboBox.addItem(StartSequence.TOGETHER.toString());
        jTable1.getColumnModel().getColumn(COL.STARTSEQUENCE.ordinal()).setCellEditor(new DefaultCellEditor(comboBox));
        //Set up tool tips for the sequence cells.
        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
        renderer.setToolTipText("Click for options");
        jTable1.getColumnModel().getColumn(COL.STARTSEQUENCE.ordinal()).setCellRenderer(renderer);

        jTable1.getColumnModel().getColumn(COL.STARTSEQUENCE.ordinal()).setPreferredWidth(30);
        jTable1.getColumnModel().getColumn(COL.ICON.ordinal()).setMaxWidth(27);
        jTable1.getColumnModel().getColumn(COL.CHECK.ordinal()).setMaxWidth(27);
        jTable1.getColumnModel().getColumn(COL.DEVICENAME.ordinal()).setPreferredWidth(30);
        jTable1.getColumnModel().getColumn(COL.STATUS.ordinal()).setPreferredWidth(20);
        hideColumn(COL.DEVICEDESCR);
        hideColumn(COL.TESTPATH);
        hideColumn(COL.TESTDESCR);
        hideColumn(COL.TESTPARAMS);
        hideColumn(COL.TESTHELP);
        jTable1.setAutoCreateRowSorter(true);
        updateStatus();
    }

    /**
     * hide a specific column
     * @param column column to hide
     */
    private void hideColumn(COL column) {
        jTable1.getColumnModel().getColumn(column.ordinal()).setMinWidth(0);
        jTable1.getColumnModel().getColumn(column.ordinal()).setMaxWidth(0);
    }

    /**
     * append test info (see parameters) to table
     *
     * @param deviceName
     * @param deviceDescription
     * @param testSuite
     * @param testName
     * @param testParams string of parameters. See buildParametersMap() for
     * details
     * @param testDescription
     * @param testHelp
     *
     */
    private void addToTable(String deviceName,
            String deviceDescription,
            String testSuite,
            String testName,
            String testParams,
            String testDescription,
            String testHelp) {
        String sDate = "";
        if (testName.equals("") || deviceName.equals("")) {
            return;
        }
        try {
            ImageIcon icon = null;// new ImageIcon(getClass().getResource("/icons/button_pause-16px.png"));
            DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
            //String testPath = FilenameUtils.separatorsToSystem(TESTS_TESTS_DEFAULT_DIR + testSuite + "/" + testName + "/" + testName + ".py");
            String testPath = Paths.get(TESTS_TESTS_DEFAULT_DIR.toString(), testSuite, testName, testName + ".py").toString();
            Object rowData[] = new Object[]{false, "", sDate, deviceName, deviceDescription, testSuite, testName, testPath, testParams, testDescription, testHelp, "", "Pending", icon};
            //vedify that this test is not already in the table
            int totalRows = model.getRowCount();
            boolean bTestAlreadyInTable = false;
            for (int row = 0; row < totalRows; row++) {
                bTestAlreadyInTable = (model.getValueAt(row, COL.DEVICENAME.ordinal()).toString().equals(deviceName)
                        && model.getValueAt(row, COL.TESTSUITE.ordinal()).toString().equals(testSuite)
                        && model.getValueAt(row, COL.TESTNAME.ordinal()).toString().equals(testName)
                        && model.getValueAt(row, COL.TESTPATH.ordinal()).toString().equals(testPath));
                if (bTestAlreadyInTable) {
                    break;
                }
            }
            if (!bTestAlreadyInTable) {
                model.addRow(rowData);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            SwingUtils.showMessage(this, "loadListFilter()", ex.toString());
        }
        updateStatus();
    }

    /**
     * get the current time in format like 2015/07/02 15:59:43
     *
     * @return formatted time
     */
    public String getNow() {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date date = new Date();
        return dateFormat.format(date);
    }

    /**
     * get the current time in format like 20150702155943
     *
     * @return time with sortable format
     */
    public String getnow() {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date();
        return dateFormat.format(date);
    }

    /**
     * scan tests directory, scan devices directory, search for their test cases
     * and scan for tests.
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void loadTests() throws FileNotFoundException, IOException {
        Properties propDevice = new Properties();
        Properties propTest = new Properties();
        //String fileName = TESTS_DEVICES_DEFAULT_DIR;
        //File folder = new File(fileName);
        File testsFolder = null;
        String sTestName;
        int iCounter = 0;
        //search devices and their tests
        //Scan the list of devices
        File[] listOfDevices = TESTS_DEVICES_DEFAULT_DIR.toFile().listFiles();
        for (File deviceInList : listOfDevices) {
            propDevice.clear();
            if (deviceInList.isFile()) {
            } else if (deviceInList.isDirectory()) {
                File configFile = new File(deviceInList.getPath() + FilenameUtils.separatorsToSystem("/" + TESTS_CONFIG_FILENAME));
                if (configFile.exists() && !configFile.isDirectory()) {
                    InputStream is = new FileInputStream(configFile);
                    propDevice.load(is);
                    is.close();
                    //config of device was loaded. now load the config of each test belonging to the device
                    testsFolder = Paths.get(TESTS_TESTS_DEFAULT_DIR.toString(), propDevice.getProperty("tests")).toFile();
                    if (testsFolder.exists() && testsFolder.isDirectory()) {
                        File[] listOfTests = testsFolder.listFiles();
                        for (File testinList : listOfTests) {
                            propTest.clear();
                            if (testinList.isDirectory()) {
                                configFile = new File(testinList.getPath() + FilenameUtils.separatorsToSystem("/" + TESTS_CONFIG_FILENAME));
                                if (configFile.exists() && !configFile.isDirectory()) {
                                    InputStream ist = new FileInputStream(configFile);
                                    propTest.load(ist);
                                    ist.close();
                                    addToTable(propDevice.getProperty("name"),
                                            propDevice.getProperty("description"),
                                            propDevice.getProperty("tests"),
                                            propTest.getProperty("name"),
                                            "", //test parameters
                                            "", //test description
                                            propTest.getProperty("help"));
                                    iCounter++;
                                }
                            }
                        }
                    }
                }
            }
        }          
        //showEnabledTestsOnly(jCheckBoxMenuShowSelectedTests1.getState());
        logger.log(Level.INFO, iCounter + " tests loaded.");
    }

    /**
     * scan tests table and see if tests/devices in the table still exist in directory.
     * Remove from table the tests whose files do not exist in directories.
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void cleanTests() throws FileNotFoundException, IOException {
        Properties propDevice = new Properties();
        Properties propTest = new Properties();
        //String fileName = TESTS_DEVICES_DEFAULT_DIR;
        //File folder = new File(fileName);
        File testsFolder = null;
        String sTestName;
        int iCounter = 0;
        //search devices and their tests
        //Scan the list of devices
        File[] listOfDevices = TESTS_DEVICES_DEFAULT_DIR.toFile().listFiles();
        //collect all tests from files
        List<List> testsInDir = new ArrayList<List>();
        List<String> testDetails = new ArrayList<String>();
        for (File deviceInList : listOfDevices) {
            propDevice.clear();
            if (deviceInList.isDirectory()) {
                File configFile = new File(deviceInList.getPath() + FilenameUtils.separatorsToSystem("/"+TESTS_CONFIG_FILENAME));
                if (configFile.exists() && !configFile.isDirectory()) {
                    InputStream is = new FileInputStream(configFile);
                    propDevice.load(is);
                    is.close();
                    //config of device was loaded. now load the config of each test belonging to the device
                    testsFolder = Paths.get(TESTS_TESTS_DEFAULT_DIR.toString(), propDevice.getProperty("tests")).toFile();
                    if (testsFolder.exists() && testsFolder.isDirectory()) {
                        File[] listOfTests = testsFolder.listFiles();
                        for (File testinList : listOfTests) {
                            propTest.clear();
                            if (testinList.isDirectory()) {
                                configFile = new File(testinList.getPath() + FilenameUtils.separatorsToSystem("/"+TESTS_CONFIG_FILENAME));
                                if (configFile.exists() && !configFile.isDirectory()) {
                                    InputStream ist = new FileInputStream(configFile);
                                    propTest.load(ist);
                                    ist.close();
                                    testDetails = new ArrayList<String>();
                                    testDetails.add(propDevice.getProperty("name"));
                                    testDetails.add(propDevice.getProperty("tests"));
                                    testDetails.add(propTest.getProperty("name"));
                                    iCounter++;
                                    testsInDir.add(testDetails);
                                }
                            }
                        }
                    }
                }
            }
        }
        //now check each test in table, if it is present in directory
        DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
        int row;
        boolean bTestAlreadyInTable = false;
        String testSuiteTable, testSuiteDir;
        String testNameTable, testNameDir;
        String deviceNameTable, deviceNameDir;
        String testPathTable, testPathDir;
        int totalRows = model.getRowCount();
        //loop among the tests in the table
        for (row = 0; row < totalRows; row++) {
            bTestAlreadyInTable = false;
            deviceNameTable = model.getValueAt(row, COL.DEVICENAME.ordinal()).toString();
            //testSuiteTable  = model.getValueAt(row, COL.TESTSUITE.ordinal()).toString();
            //testNameTable   = model.getValueAt(row, COL.TESTNAME.ordinal()).toString();
            testPathTable = model.getValueAt(row, COL.TESTPATH.ordinal()).toString();
            //loop to compare current test in table with all tests in directory
            for (int i = 0; i < testsInDir.size(); i++) {
                deviceNameDir = testsInDir.get(i).get(0).toString();
                testSuiteDir  = testsInDir.get(i).get(1).toString();
                testNameDir   = testsInDir.get(i).get(2).toString();
                testPathDir   = Paths.get(TESTS_TESTS_DEFAULT_DIR.toString(), 
                        testSuiteDir, testNameDir, testNameDir + ".py").toString();
                //check if the test files and device files corresponding to the current test row exist
                bTestAlreadyInTable = (testPathDir.equals(testPathTable) && deviceNameDir.equals(deviceNameTable));
                if (bTestAlreadyInTable) {
                    break;
                }
            }
            if (!bTestAlreadyInTable) {
                //this test in table does not exist any more in the tests directory or in the device directory: remove
                model.removeRow(row);
                totalRows = model.getRowCount();
                row=0;
            }
        }
    }

    /**
     * Build a map with optional parameters to be passed to the testing script.
     * The map is like this: parameters \_ name | \_ value | \_ description \_
     * name | \_ value | \_ description ... the name 'name' is the mapping key.
     * 'value' and 'description' are constant mapping keys of a nested map.
     *
     * @param parametersString string containing the parameters. Syntax:
     * name:value:description[;name:value:description;...]
     */
    private HashMap buildParametersMap(String parametersString) {
        HashMap mParameters = new HashMap(); // contains name and attributes
        HashMap mParameterAttributes = new HashMap(); //contians value and description
        String[] dsParameterAttributes = null;
        String[] dsParameters = parametersString.split(";");
        for (String sParameter : dsParameters) {
            dsParameterAttributes = sParameter.split(":");
            if (dsParameterAttributes.length > 2) {
                mParameterAttributes = new HashMap();
                mParameterAttributes.put("value", (Object) dsParameterAttributes[1]);
                mParameterAttributes.put("description", dsParameterAttributes[2]);
                //add parameter name and attributes (value + description)
                mParameters.put(dsParameterAttributes[0], mParameterAttributes);
            }
        }
        return mParameters;
    }

    /**
     * get the parameters from the test config file
     *
     * @param sTestPath directory where the test files are (directory with the
     * test name)
     * @return HashMap of the test parameters. See buildParametersMap() for
     * details.
     */
    private HashMap getTestParameters(String sTestPath) {
        String testParams = getConfigItem("parameters", sTestPath);
        return buildParametersMap(testParams);
    }

    /**
     * Get the description of the test.
     *
     * @param sTestPath directory where the test files are (directory with the
     * test name)
     * @return test description
     */
    private String getTestDescription(String sTestPath) {
        return getConfigItem("description", sTestPath);
    }

    /**
     * generic function that get a configuration item of a test.
     *
     * @param sParameterName configuration item name to retrieve from the test
     * @param sTestPath directory where the test files are (directory with the
     * test name)
     * @return value of the configuration item
     */
    private String getConfigItem(String sParameterName, String sTestPath) {
        Properties propTest = new Properties();
        File fileTest = new File(sTestPath);
        File dirTest = fileTest.getParentFile();
        String testParams = "";
        if (dirTest.isDirectory()) {
            File configFile = new File(dirTest.getPath() + FilenameUtils.separatorsToSystem("/"+TESTS_CONFIG_FILENAME));
            if (configFile.exists() && !configFile.isDirectory()) {
                try {
                    InputStream ist = new FileInputStream(configFile);
                    propTest.load(ist);
                    ist.close();
                    testParams = propTest.getProperty(sParameterName);
                    if (testParams == null) {
                        testParams = "";
                    }

                } catch (IOException ex) {
                    Logger.getLogger(TestingList.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        return testParams;
    }

    /**
     * Save current setup in a file. The current position of table rows is also
     * saved.
     */
    public void saveSettings() {
        if (!TESTS_CONFIG_DEFAULT_DIR.toFile().isDirectory()) {
            boolean success = TESTS_CONFIG_DEFAULT_DIR.toFile().mkdirs();
            if (!success) {
                // Directory creation failed
                SwingUtils.showMessage(this, "saveSettings()", "Cannot create directory " + TESTS_CONFIG_DEFAULT_DIR.toString());
            }
        }
        JFileChooser fc = new JFileChooser(TESTS_CONFIG_DEFAULT_DIR.toFile());
        int returnVal = fc.showSaveDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            saveSettings(file);
        }
    }

    /**
     * Save current setup in a file. The current position of table rows is also
     * saved.
     *
     * @param file file containing the settings
     */
    private void saveSettings(File file) {
        DefaultTableModel tableModel = (DefaultTableModel) jTable1.getModel();
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(file));
            out.writeObject(tableModel.getDataVector());
            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Save current setup in a file. The current position of table rows is also
     * saved.
     */
    public void loadListFilter() {
        final JFileChooser fc = new JFileChooser();
        fc.setCurrentDirectory(TESTS_CONFIG_DEFAULT_DIR.toFile());
        int returnVal = fc.showOpenDialog(this); 
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            loadListFilter(file);
            this.showEnabledTestsOnly(true);
        }
    }

    /**
     * Load current setup from a file. The current position of table rows is
     * also saved.
     *
     * @param file file containing the settings
     */
    private void loadListFilter(File file) {
        if (file == null || !file.isFile()){
            loadListFilter();
            //store current file in the properties
            saveProperties("listFilter", "");
            return;
        }
        DefaultTableModel tableModel = (DefaultTableModel) jTable1.getModel();
        try {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
            Vector rowData = (Vector) in.readObject();
            Iterator itr = rowData.iterator();
            //clear table
            tableModel.getDataVector().removeAllElements();
            tableModel.fireTableDataChanged();
            //rewrite table
            while (itr.hasNext()) {
                tableModel.addRow((Vector) itr.next());
            }
            in.close();
            loadTests();
            //store current file in the properties
            saveProperties("listFilter", file.getName());
            //try to animate the custom panel, if the tests list concerns only one device
            animateCustomPanel();
        } catch (Exception ex) {
            ex.printStackTrace();
            SwingUtils.showMessage(this, "loadListFilter()", ex.toString());
        }
    }
}
