/*******************************************************************************
 * Pentaho Data Science
 * <p/>
 * Copyright (c) 2002-2017 Hitachi Vantara. All rights reserved.
 * <p/>
 * ******************************************************************************
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.pentaho.di.ui.trans.steps.cpythonscriptexecutor;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.Props;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettlePluginException;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaFactory;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStepMeta;
import org.pentaho.di.trans.step.StepDialogInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.errorhandling.StreamInterface;
import org.pentaho.di.trans.steps.cpythonscriptexecutor.CPythonScriptExecutorMeta;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.dialog.ShowMessageDialog;
import org.pentaho.di.ui.core.gui.GUIResource;
import org.pentaho.di.ui.core.widget.ColumnInfo;
import org.pentaho.di.ui.core.widget.ComboVar;
import org.pentaho.di.ui.core.widget.StyledTextComp;
import org.pentaho.di.ui.core.widget.TableView;
import org.pentaho.di.ui.core.widget.TextVar;
import org.pentaho.di.ui.trans.step.BaseStepDialog;
import org.pentaho.python.PythonSession;

import java.util.ArrayList;
import java.util.List;

/**
 * Dialog for the CPythonScriptExecutor step
 *
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
 */
public class CPythonScriptExecutorDialog extends BaseStepDialog implements StepDialogInterface {

  private static Class<?> PKG = CPythonScriptExecutorMeta.class;

  private CTabFolder wctfContainer;

  private CTabItem wctiConfig, wctiScript, wctiFields;
  private Composite wcConfig, wcScript, wcFields;

  /**
   * Configure tab
   */
  private Group wgRowHandling, wgOptions;
  //row handling group
  private Label wlRowsToProcess, wlRowsToProcessSize, wlReservoirSampling, wlReservoirSamplingSize, wlRandomSeed;
  private ComboVar wcvRowsToProcess;
  private TextVar wtvRowsToProcessSize, wtvReservoirSamplingSize, wtvRandomSeed;
  private Button wbReservoirSampling;
  //options group
  private Label wlIncludeInputAsOutput, wlContinueOnUnsetVars;
  private Button wbIncludeInputAsOutput, wbContinueOnUnsetVars;
  //table
  private TableView wtvInputFrames;

  /**
   * Script tab
   */
  private Label wlLoadScriptFile, wlScriptLocation, wlScript;
  private Button wbLoadScriptFile, wbScriptBrowse;
  private TextVar wtvScriptLocation;
  private StyledTextComp wstcScriptEditor;

  /**
   * Fields tab
   */
  // TODO move vars to get to the script tab??
  private Label wlPyVarsToGet;
  private TextVar wtvPyVarsToGet;
  private Label wlOutputFields;
  private TableView wtvOutputFields;
  private Button wbVarsToFields;
  private Button wbGetFields;
  private Button wbIncludeRowIndex;

  private FormData fd;
  private Control lastControl;

  //constants
  private static final int FIRST_LABEL_RIGHT_PERCENTAGE = 35;
  private static final int FIRST_PROMPT_RIGHT_PERCENTAGE = 55;
  private static final int SECOND_LABEL_RIGHT_PERCENTAGE = 65;
  private static final int SECOND_PROMPT_RIGHT_PERCENTAGE = 80;
  private static final int MARGIN = Const.MARGIN;
  private static int MIDDLE;

  protected CPythonScriptExecutorMeta m_inputMeta;
  protected CPythonScriptExecutorMeta m_originalMeta;

  //listeners
  ModifyListener simpleModifyListener = new ModifyListener() {
    @Override public void modifyText( ModifyEvent e ) {
      m_inputMeta.setChanged();
    }
  };

  SelectionAdapter simpleSelectionAdapter = new SelectionAdapter() {
    @Override public void widgetDefaultSelected( SelectionEvent e ) {
      ok();
    }
  };

  public CPythonScriptExecutorDialog( Shell parent, Object inMeta, TransMeta tr, String sname ) {
    super( parent, (BaseStepMeta) inMeta, tr, sname );

    m_inputMeta = (CPythonScriptExecutorMeta) inMeta;
    m_originalMeta = (CPythonScriptExecutorMeta) m_inputMeta.clone();
  }

  @Override public String open() {

    Shell parent = getParent();
    Display display = parent.getDisplay();

    shell = new Shell( parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | SWT.MIN );
    props.setLook( shell );
    setShellImage( shell, m_inputMeta );

    changed = m_inputMeta.hasChanged();

    FormLayout formLayout = new FormLayout();
    formLayout.marginWidth = Const.FORM_MARGIN;
    formLayout.marginHeight = Const.FORM_MARGIN;

    shell.setLayout( formLayout );
    shell.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.Shell.Title" ) ); //$NON-NLS-1$

    MIDDLE = props.getMiddlePct();

    // Stepname line
    wlStepname = new Label( shell, SWT.RIGHT );
    wlStepname.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.Stepname.Label" ) ); //$NON-NLS-1$
    props.setLook( wlStepname );
    fdlStepname = new FormData();
    fdlStepname.left = new FormAttachment( 0, 0 );
    fdlStepname.right = new FormAttachment( MIDDLE, -MARGIN );
    fdlStepname.top = new FormAttachment( 0, MARGIN );
    wlStepname.setLayoutData( fdlStepname );
    wStepname = new Text( shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER );
    wStepname.setText( stepname );
    props.setLook( wStepname );
    wStepname.addModifyListener( simpleModifyListener );
    fdStepname = new FormData();
    fdStepname.left = new FormAttachment( MIDDLE, 0 );
    fdStepname.top = new FormAttachment( 0, MARGIN );
    fdStepname.right = new FormAttachment( 100, 0 );
    wStepname.setLayoutData( fdStepname );
    lastControl = wStepname;

    wctfContainer = new CTabFolder( shell, SWT.BORDER );
    props.setLook( wctfContainer, Props.WIDGET_STYLE_TAB );
    wctfContainer.setSimple( false );

    addConfigureTab();
    addScriptTab();
    addFieldsTab();
    // checkPython();

    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( wStepname, MARGIN );
    fd.right = new FormAttachment( 100, 0 );
    fd.bottom = new FormAttachment( 100, -50 );
    wctfContainer.setLayoutData( fd );

    // some buttons
    wOK = new Button( shell, SWT.PUSH );
    wOK.setText( BaseMessages.getString( PKG, "System.Button.OK" ) ); //$NON-NLS-1$
    wOK.addListener( SWT.Selection, new Listener() {
      @Override public void handleEvent( Event e ) {
        ok();
      }
    } );
    wCancel = new Button( shell, SWT.PUSH );
    wCancel.setText( BaseMessages.getString( PKG, "System.Button.Cancel" ) ); //$NON-NLS-1$
    wCancel.addListener( SWT.Selection, new Listener() {
      @Override public void handleEvent( Event e ) {
        cancel();
      }
    } );
    setButtonPositions( new Button[] { wOK, wCancel }, MARGIN, null );

    lsDef = new SelectionAdapter() {
      @Override public void widgetDefaultSelected( SelectionEvent e ) {
        ok();
      }
    };

    wStepname.addSelectionListener( lsDef );

    // Detect X or ALT-F4 or something that kills this window...
    shell.addShellListener( new ShellAdapter() {
      @Override public void shellClosed( ShellEvent e ) {
        cancel();
      }
    } );

    getData( m_inputMeta );

    m_inputMeta.setChanged( changed );

    wctfContainer.setSelection( 0 );
    // Set the shell size, based upon previous time...
    setSize();

    shell.open();
    while ( !shell.isDisposed() ) {
      if ( !display.readAndDispatch() ) {
        display.sleep();
      }
    }
    return stepname;
  }

  private void checkPython() {
    if ( !PythonSession.pythonAvailable() ) {
      // try initializing
      try {
        if ( !PythonSession.initSession( "python", transMeta, log ) ) {
          String envEvalResults = PythonSession.getPythonEnvCheckResults();
          logError(
              "Was unable to start the python environment:\n\n" + ( envEvalResults != null ? envEvalResults : "" ) );
        }
      } catch ( KettleException ex ) {
        logError( "Was unable to start the python environment:", ex );
      }
    }
  }

  private void addConfigureTab() {
    wctiConfig = new CTabItem( wctfContainer, SWT.NONE );
    wctiConfig.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ConfigTab.TabTitle" ) );

    wcConfig = new Composite( wctfContainer, SWT.NONE );
    props.setLook( wcConfig );
    FormLayout wflConfig = new FormLayout();
    wflConfig.marginWidth = 3;
    wflConfig.marginHeight = 3;
    wcConfig.setLayout( wflConfig );

    addRowHandlingGroup();
    addOptionsGroup();

    // Input Frames Label
    Label inputFramesLab = new Label( wcConfig, SWT.RIGHT );
    inputFramesLab.setText( BaseMessages.getString( PKG, "CPythonScriptExecutor.InputFrames.Label" ) );
    props.setLook( inputFramesLab );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( wgOptions, MARGIN );
    inputFramesLab.setLayoutData( fd );
    lastControl = inputFramesLab;

    // table
    ColumnInfo[]
        colinf =
        new ColumnInfo[] {
            new ColumnInfo( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.FrameNames.StepName" ),
                ColumnInfo.COLUMN_TYPE_CCOMBO, false ),
            new ColumnInfo( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.FrameNames.FrameName" ),
                ColumnInfo.COLUMN_TYPE_TEXT, false ) };

    String[] previousSteps = transMeta.getPrevStepNames( stepname );
    if ( previousSteps != null ) {
      colinf[0].setComboValues( previousSteps );
    }

    wtvInputFrames =
        new TableView( transMeta, wcConfig, SWT.FULL_SELECTION | SWT.MULTI, colinf, 1, simpleModifyListener, props );
    fd = new FormData();
    fd.top = new FormAttachment( lastControl, MARGIN );
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    wtvInputFrames.setLayoutData( fd );

    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, -MARGIN * 2 );
    fd.bottom = new FormAttachment( 100, 0 );
    wcConfig.setLayoutData( fd );

    wcConfig.layout();
    wctiConfig.setControl( wcConfig );
  }

  private void addScriptTab() {
    wctiScript = new CTabItem( wctfContainer, SWT.NONE );
    wctiScript.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ScriptTab.TabTitle" ) ); //$NON-NLS-1$
    wcScript = new Composite( wctfContainer, SWT.NONE );
    props.setLook( wcScript );
    FormLayout scriptLayout = new FormLayout();
    scriptLayout.marginWidth = 3;
    scriptLayout.marginHeight = 3;
    wcScript.setLayout( scriptLayout );

    wlLoadScriptFile = new Label( wcScript, SWT.RIGHT );
    props.setLook( wlLoadScriptFile );
    wlLoadScriptFile
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.LoadScript.Label" ) ); //$NON-NLS-1$
    wlLoadScriptFile.setLayoutData( getFirstLabelFormData() );

    wbLoadScriptFile = new Button( wcScript, SWT.CHECK );
    props.setLook( wbLoadScriptFile );
    FormData fd = getFirstPromptFormData( wlLoadScriptFile );
    fd.right = null;
    wbLoadScriptFile.setLayoutData( fd );
    wbLoadScriptFile.setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.LoadScript.TipText" ) );
    lastControl = wbLoadScriptFile;

    wbLoadScriptFile.addSelectionListener( new SelectionAdapter() {
      @Override public void widgetSelected( SelectionEvent e ) {
        checkWidgets();
      }
    } );

    wlScriptLocation = new Label( wcScript, SWT.RIGHT );
    props.setLook( wlScriptLocation );
    wlScriptLocation.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ScriptFile.Label" ) );
    wlScriptLocation.setLayoutData( getFirstLabelFormData() );

    wbScriptBrowse = new Button( wcScript, SWT.PUSH | SWT.CENTER );
    wbScriptBrowse.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.Browse.Button" ) );
    props.setLook( wbScriptBrowse );
    fd = new FormData();
    fd.right = new FormAttachment( 100, -MARGIN );
    fd.top = new FormAttachment( lastControl, MARGIN );
    wbScriptBrowse.setLayoutData( fd );

    wbScriptBrowse.addSelectionListener( new SelectionAdapter() {
      @Override public void widgetSelected( SelectionEvent e ) {
        FileDialog dialog = new FileDialog( shell, SWT.OPEN );

        if ( !Const.isEmpty( wtvScriptLocation.getText() ) ) {
          dialog.setFileName( transMeta.environmentSubstitute( wtvScriptLocation.getText() ) );
        }

        if ( dialog.open() != null ) {
          wtvScriptLocation
              .setText( dialog.getFilterPath() + System.getProperty( "file.separator" ) + dialog.getFileName() );
        }
      }
    } );

    wtvScriptLocation = new TextVar( transMeta, wcScript, SWT.SINGLE | SWT.LEFT | SWT.BORDER );
    props.setLook( wtvScriptLocation );
    fd = new FormData();
    fd.left = new FormAttachment( wlScriptLocation, MARGIN );
    fd.top = new FormAttachment( lastControl, MARGIN );
    fd.right = new FormAttachment( wbScriptBrowse, -MARGIN );
    wtvScriptLocation.setLayoutData( fd );
    lastControl = wtvScriptLocation;

    wlScript = new Label( wcScript, SWT.LEFT );
    props.setLook( wlScript );
    wlScript.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ManualScript.Label" ) );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    wlScript.setLayoutData( fd );
    lastControl = wlScript;

    wstcScriptEditor =
        new StyledTextComp( transMeta, wcScript, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL, "" );
    props.setLook( wstcScriptEditor, Props.WIDGET_STYLE_FIXED );

    wlContinueOnUnsetVars = new Label( wcScript, SWT.RIGHT );
    props.setLook( wlContinueOnUnsetVars );
    wlContinueOnUnsetVars.setText( "Continue on unset variables" );
    wlContinueOnUnsetVars.setToolTipText(
        "Don't raise an error if specified variables are not set in python after " + "script has executed" );
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( FIRST_LABEL_RIGHT_PERCENTAGE, 0 );
    wlContinueOnUnsetVars.setLayoutData( fd );

    wbContinueOnUnsetVars = new Button( wcScript, SWT.CHECK );
    props.setLook( wbContinueOnUnsetVars );
    fd = new FormData();
    fd.left = new FormAttachment( wlContinueOnUnsetVars, MARGIN );
    fd.right = new FormAttachment( SECOND_PROMPT_RIGHT_PERCENTAGE, 0 );
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    wbContinueOnUnsetVars.setLayoutData( fd );

    wlPyVarsToGet = new Label( wcScript, SWT.RIGHT );
    props.setLook( wlPyVarsToGet );
    wlPyVarsToGet.setText( "Python Variables to Get:" );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( FIRST_LABEL_RIGHT_PERCENTAGE, 0 );
    fd.bottom = new FormAttachment( wbContinueOnUnsetVars, -MARGIN );
    wlPyVarsToGet.setLayoutData( fd );

    wtvPyVarsToGet = new TextVar( transMeta, wcScript, SWT.SINGLE | SWT.LEAD | SWT.BORDER );
    props.setLook( wtvPyVarsToGet );
    fd = new FormData();
    fd.left = new FormAttachment( wlPyVarsToGet, MARGIN );
    fd.right = new FormAttachment( SECOND_PROMPT_RIGHT_PERCENTAGE, 0 );
    fd.bottom = new FormAttachment( wbContinueOnUnsetVars, -MARGIN );
    wtvPyVarsToGet.setLayoutData( fd );
    wtvPyVarsToGet.addFocusListener( new FocusAdapter() {
      @Override public void focusLost( FocusEvent e ) {
        super.focusLost( e );
        String currVars = wtvPyVarsToGet.getText();
        if ( !Const.isEmpty( currVars ) ) {
          List<String> varList = stringToList( currVars );
          wbGetFields.setEnabled( varList.size() == 1 );
          wbIncludeRowIndex.setEnabled( varList.size() == 1 );
        }
      }
    } );

    /* Button wTestScriptScript = new Button( wcScript, SWT.PUSH );
    wTestScriptScript.setText( BaseMessages.getString( PKG, "RScriptExecutorDialog.TestScript.Button" ) );
    wTestScriptScript.setToolTipText(
        BaseMessages.getString( PKG, "RScriptExecutorDialog.TestScript.Button.TipText" ) );
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( 0, 0 );
    wTestScriptScript.setLayoutData( fd );
    wTestScriptScript.addListener( SWT.Selection, new Listener() {
      @Override
      public void handleEvent( Event e ) {
        CPythonScriptExecutorMeta meta = new CPythonScriptExecutorMeta();
        getInfo( meta );
        testScript( meta );
      }
    } ); */

    wstcScriptEditor.addModifyListener( simpleModifyListener );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    fd.right = new FormAttachment( 100, -2 * MARGIN );
    // TODO might have to fix this bottom setting somehow
    fd.bottom = new FormAttachment( wtvPyVarsToGet, -MARGIN );
    wstcScriptEditor.setLayoutData( fd );

    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    // TODO might have to fix this bottom setting somehow
    fd.bottom = new FormAttachment( wtvPyVarsToGet, 0 );
    wcScript.setLayoutData( fd );

    wcScript.layout();
    wctiScript.setControl( wcScript );
  }

  private void addFieldsTab() {
    // --- fields tab
    wctiFields = new CTabItem( wctfContainer, SWT.NONE );
    wctiFields.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.FieldsTab.TabTitle" ) ); //$NON-NLS-1$
    wcFields = new Composite( wctfContainer, SWT.NONE );
    props.setLook( wcFields );
    FormLayout fieldsLayout = new FormLayout();
    fieldsLayout.marginWidth = 3;
    fieldsLayout.marginHeight = 3;
    wcFields.setLayout( fieldsLayout );

    wlOutputFields = new Label( wcFields, SWT.LEFT );
    wlOutputFields
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.OutFields.Label" ) ); //$NON-NLS-1$
    props.setLook( wlOutputFields );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( MIDDLE, -MARGIN );
    fd.top = new FormAttachment( 0, MARGIN );
    wlOutputFields.setLayoutData( fd );
    lastControl = wlOutputFields;

    wbVarsToFields = new Button( wcFields, SWT.PUSH );
    wbVarsToFields
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.VarsToFields.Button" ) ); //$NON-NLS-1$
    wbVarsToFields.setToolTipText(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.VarsToFields.Button.TipText" ) ); //$NON-NLS-1$
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( 0, 0 );
    wbVarsToFields.setLayoutData( fd );
    wbVarsToFields.addListener( SWT.Selection, new Listener() {
      @Override public void handleEvent( Event event ) {
        CPythonScriptExecutorMeta meta = new CPythonScriptExecutorMeta();
        setData( meta );
        varsToTableFields( meta );
      }
    } );
    wbGetFields = new Button( wcFields, SWT.PUSH );
    wbGetFields.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.GetFrameFields.Button" ) );
    wbGetFields
        .setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.GetFrameFields.Button.TipText" ) );
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( wbVarsToFields, MARGIN * 2 );
    wbGetFields.setLayoutData( fd );
    wbGetFields.addListener( SWT.Selection, new Listener() {
      @Override public void handleEvent( Event event ) {
        CPythonScriptExecutorMeta meta = new CPythonScriptExecutorMeta();
        setData( meta );
        getFrameFields( meta );
      }
    } );
    wbGetFields.setEnabled( false );

    Label wlIncludeRowIndex = new Label( wcFields, SWT.RIGHT );
    props.setLook( wlIncludeRowIndex );
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( wbGetFields, MARGIN * 2 );
    wlIncludeRowIndex.setLayoutData( fd );

    wbIncludeRowIndex = new Button( wcFields, SWT.CHECK );
    wbIncludeRowIndex
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.IncludeFrameRowIndex.Button" ) );
    fd = new FormData();
    fd.bottom = new FormAttachment( 100, -MARGIN * 2 );
    fd.left = new FormAttachment( wlIncludeRowIndex, MARGIN * 2 );
    wbIncludeRowIndex.setLayoutData( fd );
    wbIncludeRowIndex.setEnabled( false );

    // table
    ColumnInfo[]
        colinf2 =
        new ColumnInfo[] { new ColumnInfo( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.OutFields.Name" ),
            ColumnInfo.COLUMN_TYPE_TEXT, false ),
            new ColumnInfo( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.OutFields.Type" ),
                ColumnInfo.COLUMN_TYPE_CCOMBO, /*ValueMeta.getAllTypes()*/ ValueMetaFactory.getAllValueMetaNames(), false ) };

    wtvOutputFields =
        new TableView( transMeta, wcFields, SWT.FULL_SELECTION | SWT.MULTI, colinf2, 1, simpleModifyListener, props );
    fd = new FormData();
    fd.top = new FormAttachment( lastControl, MARGIN * 2 );
    fd.bottom = new FormAttachment( wbVarsToFields, -MARGIN * 2 );
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    wtvOutputFields.setLayoutData( fd );

    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.top = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    fd.bottom = new FormAttachment( 100, 0 );
    wcFields.setLayoutData( fd );

    wcFields.layout();
    wctiFields.setControl( wcFields );
  }

  private void addRowHandlingGroup() {
    wgRowHandling = new Group( wcConfig, SWT.SHADOW_NONE );
    props.setLook( wgRowHandling );
    wgRowHandling.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ConfigTab.RowHandlingGroup" ) );
    FormLayout wglRowHandling = new FormLayout();
    wglRowHandling.marginWidth = 10;
    wglRowHandling.marginHeight = 10;
    wgRowHandling.setLayout( wglRowHandling );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    fd.top = new FormAttachment( 0, 0 );
    wgRowHandling.setLayoutData( fd );

    addRowsToProcessControllers(); // Number of Rows to Process
    addReservoirSamplingControllers(); // Reservoir Sampling
    addRandomSeedControllers(); // Random Seed
  }

  private void getFrameFields( CPythonScriptExecutorMeta meta ) {

    try {
      meta.setOutputFields( new RowMeta() );
      List<String> frameNames = meta.getFrameNames();
      List<StreamInterface> infoStreams = meta.getStepIOMeta().getInfoStreams();
      List<RowMetaInterface> incomingMetas = new ArrayList<RowMetaInterface>();
      if ( frameNames.size() > 0 && infoStreams.size() > 0 ) {

        for ( int i = 0; i < infoStreams.size(); i++ ) {
          incomingMetas.add( transMeta.getStepFields( infoStreams.get( i ).getStepMeta() ) );
        }
      }

      ShowMessageDialog
          smd =
          new ShowMessageDialog( this.getParent(), SWT.YES | SWT.NO | SWT.ICON_WARNING,
              BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.GetFields.Dialog.Title" ),
              BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.GetFields.Dialog.Message" ), false );
      int buttonID = smd.open();

      if ( buttonID == SWT.YES ) {
        RowMetaInterface rowMeta = new RowMeta();
        meta.getFields( rowMeta, "bogus", incomingMetas.toArray( new RowMetaInterface[incomingMetas.size()] ), null,
            transMeta, null, null );

        wtvOutputFields.clearAll();
        for ( int i = 0; i < rowMeta.size(); i++ ) {
          TableItem item = new TableItem( wtvOutputFields.table, SWT.NONE );
          item.setText( 1, Const.NVL( rowMeta.getValueMeta( i ).getName(), "" ) );
          item.setText( 2, Const.NVL( rowMeta.getValueMeta( i ).getTypeDesc(), "" ) );
        }
        wtvOutputFields.removeEmptyRows();
        wtvOutputFields.setRowNums();
        wtvOutputFields.optWidth( true );
      }
    } catch ( KettleException ex ) {
      new ErrorDialog( shell, stepname, BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ErrorGettingFields" ),
          ex );
    }
  }

  private void addRowsToProcessControllers() {
    wlRowsToProcess = new Label( wgRowHandling, SWT.RIGHT );
    wlRowsToProcess.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Label" ) );
    props.setLook( wlRowsToProcess );
    fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( FIRST_LABEL_RIGHT_PERCENTAGE, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    wlRowsToProcess.setLayoutData( getFirstLabelFormData() );

    wcvRowsToProcess = new ComboVar( transMeta, wgRowHandling, SWT.BORDER | SWT.READ_ONLY );
    props.setLook( wcvRowsToProcess );
    wcvRowsToProcess.setEditable( false );
    wcvRowsToProcess.addSelectionListener( new SelectionAdapter() {
      @Override public void widgetSelected( SelectionEvent e ) {
        m_inputMeta.setChanged();
        handleRowsToProcessChange();
        if ( wtvInputFrames.getItemCount() > 1 && wbReservoirSampling.getSelection() && wcvRowsToProcess.getText()
            .equals( BaseMessages.getString( PKG,
                "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.RowByRowEntry.Label" ) ) ) {
          ShowMessageDialog
              smd =
              new ShowMessageDialog( shell, SWT.OK | SWT.ICON_WARNING,
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.RowByRowWarning.Title" ),
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.RowByRowWarning.Message" ), false );
          smd.open();
        }
      }
    } );
    wcvRowsToProcess.setLayoutData( getFirstPromptFormData( wlRowsToProcess ) );
    wcvRowsToProcess.add(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.AllEntry.Label" ) );
    wcvRowsToProcess.add( BaseMessages
        .getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.RowByRowEntry.Label" ) );
    wcvRowsToProcess.add(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.BatchEntry.Label" ) );
    wcvRowsToProcess.setToolTipText(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.TipText" ) );

    wlRowsToProcessSize = new Label( wgRowHandling, SWT.RIGHT );
    wlRowsToProcessSize
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Size.Label" ) );
    props.setLook( wlRowsToProcessSize );
    wlRowsToProcessSize.setLayoutData( getSecondLabelFormData( wcvRowsToProcess ) );

    wtvRowsToProcessSize = new TextVar( transMeta, wgRowHandling, SWT.SINGLE | SWT.LEAD | SWT.BORDER );
    props.setLook( wtvRowsToProcessSize );
    wtvRowsToProcessSize.addModifyListener( simpleModifyListener );
    wtvRowsToProcessSize.setLayoutData( getSecondPromptFormData( wlRowsToProcessSize ) );
    wtvRowsToProcessSize.setEnabled( false );
    lastControl = wtvRowsToProcessSize;
    wtvRowsToProcessSize.setToolTipText(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Size.TipText" ) );
  }

  private void addReservoirSamplingControllers() {
    //reservoir sampling
    wlReservoirSampling = new Label( wgRowHandling, SWT.RIGHT );
    wlReservoirSampling.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ReservoirSampling.Label" ) );
    props.setLook( wlReservoirSampling );
    wlReservoirSampling.setLayoutData( getFirstLabelFormData() );

    wbReservoirSampling = new Button( wgRowHandling, SWT.CHECK );
    props.setLook( wbReservoirSampling );
    FormData fd = getFirstPromptFormData( wlReservoirSampling );
    fd.right = null;
    wbReservoirSampling.setLayoutData( fd );
    wbReservoirSampling.addSelectionListener( new SelectionAdapter() {
      @Override public void widgetSelected( SelectionEvent e ) {
        m_inputMeta.setChanged();
        handleReservoirSamplingChange();
        if ( wtvInputFrames.getItemCount() > 1 && wbReservoirSampling.getSelection() && wcvRowsToProcess.getText()
            .equals( BaseMessages.getString( PKG,
                "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.RowByRowEntry.Label" ) ) ) {
          ShowMessageDialog
              smd =
              new ShowMessageDialog( shell, SWT.OK | SWT.ICON_WARNING,
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.RowByRowWarning.Title" ),
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.RowByRowWarning.Message" ), false );
          smd.open();
        }
      }
    } );
    wbReservoirSampling
        .setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ReservoirSampling.TipText" ) );

    wlReservoirSamplingSize = new Label( wgRowHandling, SWT.RIGHT );
    wlReservoirSamplingSize
        .setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ReservoirSampling.Size.Label" ) );
    props.setLook( wlReservoirSamplingSize );
    wlReservoirSamplingSize.setLayoutData( getSecondLabelFormData( wbReservoirSampling ) );

    wtvReservoirSamplingSize = new TextVar( transMeta, wgRowHandling, SWT.SINGLE | SWT.LEFT | SWT.BORDER );
    wtvReservoirSamplingSize
        .setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ReservoirSampling.Size.TipText" ) );
    props.setLook( wtvReservoirSamplingSize );
    wtvReservoirSamplingSize.setLayoutData( getSecondPromptFormData( wlReservoirSamplingSize ) );
    wtvReservoirSamplingSize.setEnabled( false );
    lastControl = wtvReservoirSamplingSize;
    wtvReservoirSamplingSize.addModifyListener( new ModifyListener() {
      @Override public void modifyText( ModifyEvent e ) {
        m_inputMeta.setChanged();
      }
    } );
  }

  private void addRandomSeedControllers() {
    // random seed
    wlRandomSeed = new Label( wgRowHandling, SWT.RIGHT );
    wlRandomSeed.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.Seed.Label" ) );
    props.setLook( wlRandomSeed );
    wlRandomSeed.setLayoutData( getFirstLabelFormData() );

    wtvRandomSeed = new TextVar( transMeta, wgRowHandling, SWT.SINGLE | SWT.LEAD | SWT.BORDER );
    props.setLook( wtvRandomSeed );
    wtvRandomSeed.addModifyListener( simpleModifyListener );
    wtvRandomSeed.setLayoutData( getFirstPromptFormData( wlRandomSeed ) );
    lastControl = wtvRandomSeed;
    wtvRandomSeed.setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.Seed.TipText" ) );
  }

  /**
   * Handles the change of the dropdown and performs the enable/disable of size, reservoir sampling and reservoir
   * sampling size
   */
  private void handleRowsToProcessChange() {
    //check and disable the size input if batch isn't selected
    String wcvRowsToProcessValue = wcvRowsToProcess.getText();
    if ( wcvRowsToProcessValue.equals( BaseMessages
        .getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.BatchEntry.Label" ) ) ) {
      wtvRowsToProcessSize.setEnabled( true );
      setItemText( wtvRowsToProcessSize, m_originalMeta.getRowsToProcessSize() );

      //reset the other controllers
      wbReservoirSampling.setEnabled( false );
      wbReservoirSampling.setSelection( false );
      wtvReservoirSamplingSize.setEnabled( false );
      wtvReservoirSamplingSize.setText( "" );
      wtvRandomSeed.setEnabled( false );
      wtvRandomSeed.setText( "" );
    } else if ( wcvRowsToProcessValue.equals(
        BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.AllEntry.Label" ) ) ) {
      wtvRowsToProcessSize.setEnabled( false );
      wtvRowsToProcessSize.setText( "" );

      //reset the other controllers
      wbReservoirSampling.setEnabled( true );
      wbReservoirSampling.setSelection( m_originalMeta.getDoingReservoirSampling() );
      wtvReservoirSamplingSize.setEnabled( wbReservoirSampling.getSelection() );
      setItemText( wtvReservoirSamplingSize, m_originalMeta.getReservoirSamplingSize() );
      wtvRandomSeed.setEnabled( m_originalMeta.getDoingReservoirSampling() );
      wtvRandomSeed.setText( m_originalMeta.getRandomSeed() == null ? "" : m_originalMeta.getRandomSeed());
    } else if ( wcvRowsToProcessValue.equals( BaseMessages
        .getString( PKG, "CPythonScriptExecutorDialog.NumberOfRowsToProcess.Dropdown.RowByRowEntry.Label" ) ) ) {
      wtvRowsToProcessSize.setEnabled( false );
      wtvRowsToProcessSize.setText( "" );

      //reset the other controllers
      wbReservoirSampling.setEnabled( true );
      wbReservoirSampling.setSelection( m_originalMeta.getDoingReservoirSampling() );
      wtvReservoirSamplingSize.setEnabled( wbReservoirSampling.getSelection() );
      setItemText(wtvReservoirSamplingSize, m_originalMeta.getReservoirSamplingSize() );
      wtvRandomSeed.setEnabled( m_originalMeta.getDoingReservoirSampling() );
      setItemText( wtvRandomSeed, m_originalMeta.getRandomSeed() );
    }
  }

  private void addOptionsGroup() {
    // add second group
    wgOptions = new Group( wcConfig, SWT.SHADOW_NONE );
    props.setLook( wgOptions );
    wgOptions.setText( BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ConfigTab.OptionsGroup" ) );
    FormLayout optionsGroupLayout = new FormLayout();
    optionsGroupLayout.marginWidth = 10;
    optionsGroupLayout.marginHeight = 10;
    wgOptions.setLayout( optionsGroupLayout );
    FormData fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    fd.top = new FormAttachment( wgRowHandling, MARGIN );
    wgOptions.setLayoutData( fd );

    addIncludeInputInOutputControllers();
  }

  private void addIncludeInputInOutputControllers() {
    // Input Fields as Output Fields
    wlIncludeInputAsOutput = new Label( wgOptions, SWT.RIGHT );
    wlIncludeInputAsOutput.setText( BaseMessages.getString( PKG, "CPythonScriptExecutor.InputFieldAsOutput.Label" ) );

    props.setLook( wlIncludeInputAsOutput );
    wlIncludeInputAsOutput.setLayoutData( getFirstLabelFormData() );

    wbIncludeInputAsOutput = new Button( wgOptions, SWT.CHECK );
    props.setLook( wbIncludeInputAsOutput );
    FormData fd = getFirstPromptFormData( wlIncludeInputAsOutput );
    fd.right = null;
    wbIncludeInputAsOutput.setLayoutData( fd );
    lastControl = wbIncludeInputAsOutput;
    wbIncludeInputAsOutput.addSelectionListener( new SelectionAdapter() {
      @Override public void widgetSelected( SelectionEvent e ) {
        if ( wbIncludeInputAsOutput.getSelection() ) {
          ShowMessageDialog
              smd =
              new ShowMessageDialog( getParent(), SWT.OK | SWT.ICON_WARNING,
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.InputFieldAsOutput.Dialog.Title" ),
                  BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.InputFieldAsOutput.Dialog.Message" ),
                  false );
          smd.open();
        }
        m_inputMeta.setChanged();
      }
    } );
    wbIncludeInputAsOutput
        .setToolTipText( BaseMessages.getString( PKG, "CPythonScriptExecutor.InputFieldAsOutput.TipText" ) );
  }

  protected void getData( CPythonScriptExecutorMeta meta ) {
    wcvRowsToProcess.setText( meta.getRowsToProcess() );
    setItemText( wtvRowsToProcessSize, meta.getRowsToProcessSize() );
    wbReservoirSampling.setSelection( meta.getDoingReservoirSampling() );
    setItemText( wtvReservoirSamplingSize, meta.getReservoirSamplingSize() );
    setItemText( wtvRandomSeed, meta.getRandomSeed());
    wbIncludeInputAsOutput.setSelection( meta.getIncludeInputAsOutput() );
    setItemText(wtvPyVarsToGet, listToString( meta.getPythonVariablesToGet() ) );
    wbContinueOnUnsetVars.setSelection( meta.getContinueOnUnsetVars() );
    wstcScriptEditor.setText( meta.getScript() == null ? "" : meta.getScript() ); //$NON-NLS-1$
    wbLoadScriptFile.setSelection( meta.getLoadScriptAtRuntime() );
    setItemText(wtvScriptLocation, meta.getScriptToLoad());
    wbIncludeRowIndex.setSelection( meta.getIncludeFrameRowIndexAsOutputField() );

    setInputToFramesTableFields( meta );
    setOutputFieldsTableFields( meta );

    checkWidgets();
    handleRowsToProcessChange();
    handleReservoirSamplingChange();
  }

  private void varsToTableFields( CPythonScriptExecutorMeta meta ) {
    // List<RowMetaInterface> incomingMetas;
    RowMetaInterface incomingMetas = new RowMeta();
    if ( meta.getIncludeInputAsOutput() ) {
      List<String> frameNames = meta.getFrameNames();
      List<StreamInterface> infoStreams = meta.getStepIOMeta().getInfoStreams();
      if ( frameNames.size() > 0 && infoStreams.size() > 0 ) {
        // incomingMetas = new ArrayList<RowMetaInterface>();

        try {
          for ( int i = 0; i < infoStreams.size(); i++ ) {
            incomingMetas.addRowMeta( transMeta.getStepFields( infoStreams.get( i ).getStepMeta() ) );
          }
        } catch ( KettleException e ) {
          new ErrorDialog( shell, stepname,
              BaseMessages.getString( PKG, "CPythonScriptExecutorDialog.ErrorGettingFields" ), e );
          return;
        }
      }
    }

    wtvOutputFields.clearAll();
    if ( incomingMetas.size() > 0 ) {
      for ( ValueMetaInterface vm : incomingMetas.getValueMetaList() ) {
        TableItem item = new TableItem( wtvOutputFields.table, SWT.NONE );
        item.setText( 1, Const.NVL( vm.getName(), "" ) );
        item.setText( 2, Const.NVL( vm.getTypeDesc(), "" ) );
      }
    }
    String vars = wtvPyVarsToGet.getText();
    if ( !Const.isEmpty( vars ) ) {
      String[] vA = vars.split( "," );
      if ( vA.length > 0 ) {
        for ( String var : vA ) {
          TableItem item = new TableItem( wtvOutputFields.table, SWT.NONE );
          item.setText( 1, Const.NVL( var.trim(), "" ) );
          item.setText( 2, "String" );
        }
        wtvOutputFields.removeEmptyRows();
        wtvOutputFields.setRowNums();
        wtvOutputFields.optWidth( true );
      }
    }
  }

  private List<String> stringToList( String list ) {
    List<String> result = new ArrayList<String>();
    for ( String s : list.split( "," ) ) {
      if ( !Const.isEmpty( s.trim() ) ) {
        result.add( s.trim() );
      }
    }

    return result;
  }

  private String listToString( List<String> list ) {
    StringBuilder b = new StringBuilder();
    for ( String s : list ) {
      b.append( s ).append( "," );
    }
    if ( b.length() > 0 ) {
      b.setLength( b.length() - 1 );
    }

    return b.toString();
  }

  private void setData( CPythonScriptExecutorMeta meta ) {
    meta.setRowsToProcess( wcvRowsToProcess.getText() );
    meta.setRowsToProcessSize( wtvRowsToProcessSize.getText() );
    meta.setDoingReservoirSampling( wbReservoirSampling.getSelection() );
    meta.setReservoirSamplingSize( wtvReservoirSamplingSize.getText() );
    meta.setRandomSeed( wtvRandomSeed.getText() );
    meta.setContinueOnUnsetVars( wbContinueOnUnsetVars.getSelection() );
    meta.setPythonVariablesToGet( stringToList( wtvPyVarsToGet.getText() ) );
    meta.setIncludeInputAsOutput( wbIncludeInputAsOutput.getSelection() );
    meta.setScript( wstcScriptEditor.getText() );
    meta.setLoadScriptAtRuntime( wbLoadScriptFile.getSelection() );
    meta.setScriptToLoad( wtvScriptLocation.getText() );
    meta.setIncludeFrameRowIndexAsOutputField( wbIncludeRowIndex.getSelection() );

    // incoming stream/frame name data from table
    int numNonEmpty = wtvInputFrames.nrNonEmpty();
    List<String> frameNames = new ArrayList<String>();
    List<String> stepNames = new ArrayList<String>();
    meta.clearStepIOMeta();
    for ( int i = 0; i < numNonEmpty; i++ ) {
      TableItem item = wtvInputFrames.getNonEmpty( i );
      String stepName = item.getText( 1 ).trim();
      String frameName = item.getText( 2 ).trim();
      if ( !Const.isEmpty( stepName ) ) {
        if ( Const.isEmpty( frameName ) ) {
          frameName = CPythonScriptExecutorMeta.DEFAULT_FRAME_NAME_PREFIX + i;
        }
        frameNames.add( frameName );
        stepNames.add( stepName );
      }
    }

    meta.setFrameNames( frameNames );
    List<StreamInterface> infoStreams = meta.getStepIOMeta().getInfoStreams();
    for ( int i = 0; i < infoStreams.size(); i++ ) {
      StepMeta m = transMeta.findStep( stepNames.get( i ) );
      if ( m != null ) {
        System.err.println( "Found step: " + m.getName() ); //$NON-NLS-1$
      }
      infoStreams.get( i ).setStepMeta( transMeta.findStep( stepNames.get( i ) ) );
    }

    // output field data from table
    numNonEmpty = wtvOutputFields.nrNonEmpty();
    RowMetaInterface outRM = numNonEmpty > 0 ? new RowMeta() : null;
    for ( int i = 0; i < numNonEmpty; i++ ) {
      TableItem item = wtvOutputFields.getNonEmpty( i );
      String name = item.getText( 1 ).trim();
      String type = item.getText( 2 ).trim();
      if ( !Const.isEmpty( name ) && !Const.isEmpty( type ) ) {
        //ValueMetaInterface vm = new ValueMeta( name, ValueMeta.getType( type ) );
        ValueMetaInterface vm;
        try {
          vm = ValueMetaFactory.createValueMeta( name, ValueMetaFactory.getIdForValueMeta( type ) );
          outRM.addValueMeta( vm );
        } catch ( KettlePluginException e ) {
          e.printStackTrace();
        }
      }
    }

    meta.setOutputFields( outRM );
  }

  private void handleReservoirSamplingChange() {
    wtvReservoirSamplingSize.setEnabled( wbReservoirSampling.getSelection() );
    wtvRandomSeed.setEnabled( wbReservoirSampling.getSelection() );
  }

  protected void checkWidgets() {
    wtvScriptLocation.setEnabled( wbLoadScriptFile.getSelection() );
    wstcScriptEditor.setEnabled( !wbLoadScriptFile.getSelection() );
    if ( wbLoadScriptFile.getSelection() ) {
      wtvScriptLocation.setEditable( true );
      wstcScriptEditor.getStyledText().setBackground( GUIResource.getInstance().getColorDemoGray() );
    } else {
      wtvScriptLocation.setEditable( false );
      wstcScriptEditor.getStyledText().setBackground( GUIResource.getInstance().getColorWhite() );
    }
    wbScriptBrowse.setEnabled( wbLoadScriptFile.getSelection() );

    String currVars = wtvPyVarsToGet.getText();
    if ( !Const.isEmpty( currVars ) ) {
      List<String> varList = stringToList( currVars );
      wbGetFields.setEnabled( varList.size() == 1 );
      wbIncludeRowIndex.setEnabled( varList.size() == 1 );
    }
  }

  protected void setInputToFramesTableFields( CPythonScriptExecutorMeta meta ) {
    List<String> frameNames = meta.getFrameNames();
    List<StreamInterface> infoStreams = meta.getStepIOMeta().getInfoStreams();

    wtvInputFrames.clearAll();
    for ( int i = 0; i < infoStreams.size(); i++ ) {
      String stepName = infoStreams.get( i ).getStepname();
      String frameName = frameNames.get( i );

      TableItem item = new TableItem( wtvInputFrames.table, SWT.NONE );
      item.setText( 1, Const.NVL( stepName, "" ) ); //$NON-NLS-1$
      item.setText( 2, Const.NVL( frameName, "" ) ); //$NON-NLS-1$
    }

    wtvInputFrames.removeEmptyRows();
    wtvInputFrames.setRowNums();
    wtvInputFrames.optWidth( true );
  }

  protected void setOutputFieldsTableFields( CPythonScriptExecutorMeta meta ) {
    RowMetaInterface outFields = meta.getOutputFields();

    if ( outFields != null && outFields.size() > 0 ) {
      for ( int i = 0; i < outFields.size(); i++ ) {
        ValueMetaInterface vm = outFields.getValueMeta( i );
        String name = vm.getName();
        String type = vm.getTypeDesc();

        TableItem item = new TableItem( wtvOutputFields.table, SWT.NONE );
        item.setText( 1, Const.NVL( name, "" ) ); //$NON-NLS-1$
        item.setText( 2, Const.NVL( type, "" ) ); //$NON-NLS-1$
      }

      wtvOutputFields.removeEmptyRows();
      wtvOutputFields.setRowNums();
      wtvOutputFields.optWidth( true );
    }
  }

  private void cancel() {
    stepname = null;
    m_inputMeta.setChanged( changed );
    dispose();
  }

  /**
   * Ok general method
   */
  private void ok() {
    if ( Const.isEmpty( wStepname.getText() ) ) {
      return;
    }

    stepname = wStepname.getText(); // return value

    setData( m_inputMeta );
    if ( !m_originalMeta.equals( m_inputMeta ) ) {
      m_inputMeta.setChanged();
      changed = m_inputMeta.hasChanged();
    }

    dispose();
  }

  private FormData getFirstLabelFormData() {
    FormData fd = new FormData();
    fd.left = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( FIRST_LABEL_RIGHT_PERCENTAGE, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    return fd;
  }

  private FormData getSecondLabelFormData( Control prevControl ) {
    FormData fd = new FormData();
    fd.left = new FormAttachment( prevControl, 0 );
    fd.right = new FormAttachment( SECOND_LABEL_RIGHT_PERCENTAGE, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    return fd;
  }

  private FormData getFirstPromptFormData( Control prevControl ) {
    FormData fd = new FormData();
    fd.left = new FormAttachment( prevControl, MARGIN );
    fd.right = new FormAttachment( FIRST_PROMPT_RIGHT_PERCENTAGE, 0 );
    fd.top = new FormAttachment( lastControl, MARGIN );
    return fd;
  }

  private FormData getSecondPromptFormData( Control prevControl ) {
    FormData fd = new FormData();
    fd.left = new FormAttachment( prevControl, MARGIN );
    fd.top = new FormAttachment( lastControl, MARGIN );
    fd.right = new FormAttachment( SECOND_PROMPT_RIGHT_PERCENTAGE, 0 );
    return fd;
  }

  /**
   * SWT TextVar's throw IllegalArgumentExceptions for null.  Defend against this.
   *
   * @param item - A TextVar, but could be generalized as needed.
   * @param value - Value to set
   */
  private void setItemText(TextVar item, String value) {
    item.setText(value == null ? "": value);
  }
}