/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * 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
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.spoon.job;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.Props;
import org.pentaho.di.core.RowMetaAndData;
import org.pentaho.di.core.database.Database;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.logging.JobEntryLogTable;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogStatus;
import org.pentaho.di.core.logging.LogTableField;
import org.pentaho.di.core.logging.LogTableInterface;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMeta;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.job.entry.JobEntryCopy;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.gui.GUIResource;
import org.pentaho.di.ui.core.widget.ColumnInfo;
import org.pentaho.di.ui.core.widget.TableView;
import org.pentaho.di.ui.spoon.Spoon;
import org.pentaho.di.ui.spoon.XulSpoonResourceBundle;
import org.pentaho.di.ui.spoon.XulSpoonSettingsManager;
import org.pentaho.di.ui.spoon.delegates.SpoonDelegate;
import org.pentaho.di.ui.xul.KettleXulLoader;
import org.pentaho.ui.xul.XulDomContainer;
import org.pentaho.ui.xul.XulLoader;
import org.pentaho.ui.xul.components.XulToolbarbutton;
import org.pentaho.ui.xul.containers.XulToolbar;
import org.pentaho.ui.xul.impl.XulEventHandler;

public class JobHistoryDelegate extends SpoonDelegate implements XulEventHandler {
  private static Class<?> PKG = JobGraph.class; // for i18n purposes, needed by Translator2!!

  private static final String XUL_FILE_TRANS_GRID_TOOLBAR = "ui/job-history-toolbar.xul";

  private JobGraph jobGraph;

  private CTabItem jobHistoryTab;

  private XulToolbar toolbar;

  private Composite jobHistoryComposite;

  private JobMeta jobMeta;

  private CTabFolder tabFolder;

  private XulToolbarbutton refreshButton;
  private XulToolbarbutton fetchNextBatchButton;
  private XulToolbarbutton fetchAllButton;

  private JobHistoryLogTab[] models;

  private enum Mode {
    INITIAL, NEXT_BATCH, ALL
  }

  /**
   * @param spoon
   *          Spoon instance
   * @param jobGraph
   *          JobGraph instance
   */
  public JobHistoryDelegate( Spoon spoon, JobGraph jobGraph ) {
    super( spoon );
    this.jobGraph = jobGraph;
  }

  public void addJobHistory() {
    // First, see if we need to add the extra view...
    //
    if ( jobGraph.extraViewComposite == null || jobGraph.extraViewComposite.isDisposed() ) {
      jobGraph.addExtraView();
    } else {
      if ( jobHistoryTab != null && !jobHistoryTab.isDisposed() ) {
        // just set this one active and get out...
        //
        jobGraph.extraViewTabFolder.setSelection( jobHistoryTab );
        return;
      }
    }

    jobMeta = jobGraph.getManagedObject();

    // Add a tab to display the logging history tables...
    //
    jobHistoryTab = new CTabItem( jobGraph.extraViewTabFolder, SWT.NONE );
    jobHistoryTab.setImage( GUIResource.getInstance().getImageShowHistory() );
    jobHistoryTab.setText( BaseMessages.getString( PKG, "Spoon.TransGraph.HistoryTab.Name" ) );

    // Create a composite, slam everything on there like it was in the history tab.
    //
    jobHistoryComposite = new Composite( jobGraph.extraViewTabFolder, SWT.NONE );
    jobHistoryComposite.setLayout( new FormLayout() );
    spoon.props.setLook( jobHistoryComposite );

    addToolBar();

    Control toolbarControl = (Control) toolbar.getManagedObject();

    toolbarControl.setLayoutData( new FormData() );
    FormData fd = new FormData();
    fd.left = new FormAttachment( 0, 0 ); // First one in the left top corner
    fd.top = new FormAttachment( 0, 0 );
    fd.right = new FormAttachment( 100, 0 );
    toolbarControl.setLayoutData( fd );

    toolbarControl.setParent( jobHistoryComposite );

    addLogTableTabs();
    tabFolder.setSelection( 0 );

    tabFolder.addSelectionListener( new SelectionListener() {
      @Override
      public void widgetSelected( SelectionEvent arg0 ) {
        setMoreRows( true );
      }

      @Override
      public void widgetDefaultSelected( SelectionEvent arg0 ) {
      }
    } );

    jobHistoryComposite.pack();
    jobHistoryTab.setControl( jobHistoryComposite );
    jobGraph.extraViewTabFolder.setSelection( jobHistoryTab );

    if ( !Props.getInstance().disableInitialExecutionHistory() ) {
      refreshAllHistory();
    }
  }

  private void addLogTableTabs() {
    // Create a nested tab folder in the tab item, on the history composite...
    //
    tabFolder = new CTabFolder( jobHistoryComposite, SWT.MULTI );
    spoon.props.setLook( tabFolder, Props.WIDGET_STYLE_TAB );

    FormData fdTabFolder = new FormData();
    fdTabFolder.left = new FormAttachment( 0, 0 ); // First one in the left top corner
    fdTabFolder.top = new FormAttachment( (Control) toolbar.getManagedObject(), 0 );
    fdTabFolder.right = new FormAttachment( 100, 0 );
    fdTabFolder.bottom = new FormAttachment( 100, 0 );
    tabFolder.setLayoutData( fdTabFolder );

    models = new JobHistoryLogTab[jobMeta.getLogTables().size()];
    for ( int i = 0; i < models.length; i++ ) {
      models[i] = new JobHistoryLogTab( tabFolder, jobMeta.getLogTables().get( i ) );
    }
  }

  private void addToolBar() {
    try {
      XulLoader loader = new KettleXulLoader();
      loader.setSettingsManager( XulSpoonSettingsManager.getInstance() );
      ResourceBundle bundle = new XulSpoonResourceBundle( Spoon.class );
      XulDomContainer xulDomContainer = loader.loadXul( XUL_FILE_TRANS_GRID_TOOLBAR, bundle );
      xulDomContainer.addEventHandler( this );
      toolbar = (XulToolbar) xulDomContainer.getDocumentRoot().getElementById( "nav-toolbar" );

      refreshButton = (XulToolbarbutton) xulDomContainer.getDocumentRoot().getElementById( "refresh-history" );
      fetchNextBatchButton =
        (XulToolbarbutton) xulDomContainer.getDocumentRoot().getElementById( "fetch-next-batch-history" );
      fetchAllButton = (XulToolbarbutton) xulDomContainer.getDocumentRoot().getElementById( "fetch-all-history" );

      ToolBar swtToolBar = (ToolBar) toolbar.getManagedObject();
      spoon.props.setLook( swtToolBar, Props.WIDGET_STYLE_TOOLBAR );
      swtToolBar.layout( true, true );
    } catch ( Throwable t ) {
      log.logError( Const.getStackTracker( t ) );
      new ErrorDialog( jobHistoryComposite.getShell(),
        BaseMessages.getString( PKG, "Spoon.Exception.ErrorReadingXULFile.Title" ),
        BaseMessages.getString( PKG, "Spoon.Exception.ErrorReadingXULFile.Message", XUL_FILE_TRANS_GRID_TOOLBAR ),
        new Exception( t ) );
    }
  }

  /**
   * Public for XUL.
   */
  public void clearLogTable() {
    clearLogTable( tabFolder.getSelectionIndex() );
  }

  /**
   * User requested to clear the log table.<br>
   * Better ask confirmation
   */
  private void clearLogTable( int index ) {
    JobHistoryLogTab model = models[index];
    LogTableInterface logTable = model.logTable;

    if ( logTable.isDefined() ) {
      String schemaTable = logTable.getQuotedSchemaTableCombination();
      DatabaseMeta databaseMeta = logTable.getDatabaseMeta();

      MessageBox mb = new MessageBox( jobGraph.getShell(), SWT.YES | SWT.NO | SWT.ICON_QUESTION );
      //CHECKSTYLE:LineLength:OFF
      mb.setMessage( BaseMessages.getString( PKG, "JobGraph.Dialog.AreYouSureYouWantToRemoveAllLogEntries.Message", schemaTable ) );
      mb.setText( BaseMessages.getString( PKG, "JobGraph.Dialog.AreYouSureYouWantToRemoveAllLogEntries.Title" ) );
      if ( mb.open() == SWT.YES ) {
        Database database = new Database( loggingObject, databaseMeta );
        try {
          database.connect();
          database.truncateTable( schemaTable );
        } catch ( Exception e ) {
          new ErrorDialog( jobGraph.getShell(),
            BaseMessages.getString( PKG, "JobGraph.Dialog.ErrorClearningLoggingTable.Title" ),
            BaseMessages.getString( PKG, "JobGraph.Dialog.AreYouSureYouWantToRemoveAllLogEntries.Message" ), e );
        } finally {
          database.disconnect();

          refreshHistory();
          if ( model.logDisplayText != null ) {
            model.logDisplayText.setText( "" );
          }
        }
      }
    }
  }

  /**
   * Public for XUL.
   */
  public void replayHistory() {
    JobHistoryLogTab model = models[tabFolder.getSelectionIndex()];

    int idx = model.logDisplayTableView.getSelectionIndex();
    if ( idx >= 0 ) {
      String[] fields = model.logDisplayTableView.getItem( idx );
      int batchId = Const.toInt( fields[0], -1 );
      // String dateString = fields[13];
      // Date replayDate = XMLHandler.stringToDate(dateString);

      List<JobEntryCopyResult> results = null;
      boolean gotResults = false;

      // We check in the Job Entry Logging to see the results from all the various job entries that were executed.
      //
      JobEntryLogTable jeLogTable = jobMeta.getJobEntryLogTable();
      if ( jeLogTable.isDefined() ) {
        try {

          DatabaseMeta databaseMeta = jobMeta.getJobEntryLogTable().getDatabaseMeta();
          Database db = new Database( Spoon.loggingObject, databaseMeta );
          try {
            db.connect();
            String schemaTable =
              databaseMeta.getQuotedSchemaTableCombination( jeLogTable.getActualSchemaName(), jeLogTable
                .getActualTableName() );
            String sql =
              "SELECT * FROM "
                + schemaTable + " WHERE " + databaseMeta.quoteField( jeLogTable.getKeyField().getFieldName() )
                + " = " + batchId;

            List<Object[]> rows = db.getRows( sql, 0 );
            RowMetaInterface rowMeta = db.getReturnRowMeta();
            results = new ArrayList<JobEntryCopyResult>();

            int jobEntryNameIndex =
              rowMeta.indexOfValue( jeLogTable
                .findField( JobEntryLogTable.ID.JOBENTRYNAME.toString() ).getFieldName() );
            int jobEntryResultIndex =
              rowMeta
                .indexOfValue( jeLogTable.findField( JobEntryLogTable.ID.RESULT.toString() ).getFieldName() );
            int jobEntryErrorsIndex =
              rowMeta
                .indexOfValue( jeLogTable.findField( JobEntryLogTable.ID.ERRORS.toString() ).getFieldName() );
            LogTableField copyNrField = jeLogTable.findField( JobEntryLogTable.ID.COPY_NR.toString() );
            int jobEntryCopyNrIndex =
              copyNrField == null ? -1 : ( copyNrField.isEnabled() ? rowMeta.indexOfValue( copyNrField
                .getFieldName() ) : -1 );

            for ( Object[] row : rows ) {
              String jobEntryName = rowMeta.getString( row, jobEntryNameIndex );
              boolean jobEntryResult = rowMeta.getBoolean( row, jobEntryResultIndex );
              long errors = rowMeta.getInteger( row, jobEntryErrorsIndex );
              long copyNr = jobEntryCopyNrIndex < 0 ? 0 : rowMeta.getInteger( row, jobEntryCopyNrIndex );
              JobEntryCopyResult result =
                new JobEntryCopyResult( jobEntryName, jobEntryResult, errors, (int) copyNr );
              results.add( result );
            }

          } finally {
            db.disconnect();
          }

          gotResults = true;
        } catch ( Exception e ) {
          new ErrorDialog(
            spoon.getShell(), BaseMessages.getString(
              PKG, "JobHistoryDelegate.ReplayHistory.UnexpectedErrorReadingJobEntryHistory.Text" ),
            BaseMessages.getString(
              PKG, "JobHistoryDelegate.ReplayHistory.UnexpectedErrorReadingJobEntryHistory.Message" ), e );

        }
      } else {
        MessageBox box = new MessageBox( spoon.getShell(), SWT.ICON_ERROR | SWT.OK );
        box.setText( BaseMessages.getString( PKG, "JobHistoryDelegate.ReplayHistory.NoJobEntryTable.Text" ) );
        box.setMessage( BaseMessages.getString( PKG, "JobHistoryDelegate.ReplayHistory.NoJobEntryTable.Message" ) );
        box.open();
      }

      // spoon.executeJob(jobGraph.getManagedObject(), true, false, replayDate, false);
      if ( !gotResults ) {

        // For some reason we have no execution results, simply list all the job entries so the user can choose...
        //
        results = new ArrayList<JobEntryCopyResult>();
        for ( JobEntryCopy copy : jobMeta.getJobCopies() ) {
          results.add( new JobEntryCopyResult( copy.getName(), null, null, copy.getNr() ) );
        }
      }

      // OK, now that we have our list of job entries, let's first try to find the first job-entry that had a false
      // result or where errors>0
      // If the error was handled, we look further for a more appropriate target.
      //
      JobEntryCopy selection = null;
      boolean more = true;
      JobEntryCopy start = jobMeta.findStart();
      while ( selection == null && more ) {
        int nrNext = jobMeta.findNrNextJobEntries( start );
        more = nrNext > 0;
        for ( int n = 0; n < nrNext; n++ ) {
          JobEntryCopy copy = jobMeta.findNextJobEntry( start, n );

          // See if we can find a result for this job entry...
          //
          JobEntryCopyResult result = JobEntryCopyResult.findResult( results, copy );
          if ( result != null ) {
            System.out.println( "TODO: replay" );
            // Do nothing???
          }
        }
      }

      // Present all job entries to the user.
      //
      for ( JobEntryCopyResult result : results ) {
        System.out.println( "Job entry copy result --  Name="
          + result.getJobEntryName() + ", result=" + result.getResult() + ", errors=" + result.getErrors()
          + ", nr=" + result.getCopyNr() );
      }

    }
  }

  /**
   * Public for XUL.
   */
  public void refreshHistory() {
    refreshHistory( tabFolder.getSelectionIndex(), Mode.INITIAL );
  }

  private void refreshAllHistory() {
    for ( int i = 0; i < models.length; i++ ) {
      refreshHistory( i, Mode.INITIAL );
    }
  }

  /**
   * Background thread refreshes history data
   */
  private void refreshHistory( final int index, final Mode fetchMode ) {
    new Thread( new Runnable() {
      public void run() {
        // do gui stuff here
        spoon.getDisplay().syncExec( new Runnable() {
          public void run() {
            setQueryInProgress( true );
            JobHistoryLogTab model = models[index];
            model.setLogTable( jobMeta.getLogTables().get( index ) );
          }
        } );

        final boolean moreRows = getHistoryData( index, fetchMode );

        // do gui stuff here
        spoon.getDisplay().syncExec( new Runnable() {
          public void run() {
            displayHistoryData( index );
            setQueryInProgress( false );
            setMoreRows( moreRows );
          }
        } );

      }
    } ).start();
  }

  private void setMoreRows( final boolean moreRows ) {
    fetchNextBatchButton.setDisabled( !moreRows );
  }

  /**
   * Don't allow more queries until this one finishes.
   *
   * @param inProgress
   *          is query in progress
   */
  private void setQueryInProgress( final boolean inProgress ) {
    refreshButton.setDisabled( inProgress );
    fetchNextBatchButton.setDisabled( inProgress );
    fetchAllButton.setDisabled( inProgress );
  }

  private boolean getHistoryData( final int index, final Mode mode ) {
    final int BATCH_SIZE = Props.getInstance().getLinesInHistoryFetchSize();
    boolean moreRows = false;
    JobHistoryLogTab model = models[index];
    LogTableInterface logTable = model.logTable;
    // See if there is a job loaded that has a connection table specified.
    //
    if ( jobMeta != null && !Const.isEmpty( jobMeta.getName() ) && logTable.isDefined() ) {
      Database database = null;
      try {
        DatabaseMeta logConnection = logTable.getDatabaseMeta();

        // open a connection
        database = new Database( loggingObject, logConnection );
        database.shareVariablesWith( jobMeta );
        database.connect();

        int queryLimit = 0;

        switch ( mode ) {
          case ALL:
            model.batchCount = 0;
            queryLimit = Props.getInstance().getMaxNrLinesInHistory();
            break;
          case NEXT_BATCH:
            model.batchCount++;
            queryLimit = BATCH_SIZE * model.batchCount;
            break;
          case INITIAL:
            model.batchCount = 1;
            queryLimit = BATCH_SIZE;
            break;
          default:
            break;
        }
        database.setQueryLimit( queryLimit );

        // First, we get the information out of the database table...
        //
        String schemaTable = logTable.getQuotedSchemaTableCombination();

        StringBuilder sql = new StringBuilder( "SELECT " );
        boolean first = true;
        for ( LogTableField field : logTable.getFields() ) {
          if ( field.isEnabled() && field.isVisible() ) {
            if ( !first ) {
              sql.append( ", " );
            }
            first = false;
            sql.append( logConnection.quoteField( field.getFieldName() ) );
          }
        }
        sql.append( " FROM " ).append( schemaTable );

        RowMetaAndData params = new RowMetaAndData();

        // Do we need to limit the amount of data?
        //
        LogTableField nameField = logTable.getNameField();
        LogTableField keyField = logTable.getKeyField();

        if ( nameField != null ) {
          sql
            .append( " WHERE " ).append( logConnection.quoteField( nameField.getFieldName() ) ).append(
              " LIKE ?" );
          params
            .addValue( new ValueMeta( "transname_literal", ValueMetaInterface.TYPE_STRING ), jobMeta.getName() );
        }

        if ( keyField != null && keyField.isEnabled() ) {
          sql
            .append( " ORDER BY " ).append( logConnection.quoteField( keyField.getFieldName() ) ).append(
              " DESC" );
        }

        ResultSet resultSet = database.openQuery( sql.toString(), params.getRowMeta(), params.getData() );

        List<Object[]> rows = new ArrayList<Object[]>();
        Object[] rowData = database.getRow( resultSet );
        int rowsFetched = 1;
        while ( rowData != null ) {
          rows.add( rowData );
          rowData = database.getRow( resultSet );
          rowsFetched++;
        }

        if ( rowsFetched >= queryLimit ) {
          moreRows = true;
        }

        database.closeQuery( resultSet );

        models[index].rows = rows;
      } catch ( Exception e ) {
        LogChannel.GENERAL.logError( "Unable to get rows of data from logging table " + models[index].logTable, e );
        models[index].rows = new ArrayList<Object[]>();
      } finally {
        if ( database != null ) {
          database.disconnect();
        }
      }
    } else {
      models[index].rows = new ArrayList<Object[]>();
    }
    return moreRows;
  }

  private void displayHistoryData( final int index ) {
    JobHistoryLogTab model = models[index];
    ColumnInfo[] colinf = model.logDisplayTableView.getColumns();

    // Now, we're going to display the data in the table view
    //
    if ( model.logDisplayTableView == null || model.logDisplayTableView.isDisposed() ) {
      return;
    }

    int selectionIndex = model.logDisplayTableView.getSelectionIndex();

    model.logDisplayTableView.table.clearAll();

    List<Object[]> rows = model.rows;

    if ( rows != null && rows.size() > 0 ) {
      // OK, now that we have a series of rows, we can add them to the table view...
      //
      for ( Object[] rowData : rows ) {
        TableItem item = new TableItem( model.logDisplayTableView.table, SWT.NONE );

        for ( int c = 0; c < colinf.length; c++ ) {

          ColumnInfo column = colinf[c];

          ValueMetaInterface valueMeta = column.getValueMeta();
          String string = null;
          try {
            string = valueMeta.getString( rowData[c] );
          } catch ( KettleValueException e ) {
            log.logError( "history data conversion issue", e );
          }
          item.setText( c + 1, Const.NVL( string, "" ) );
        }

        // Add some color
        //
        Long errors = null;
        LogStatus status = null;

        LogTableField errorsField = model.logTable.getErrorsField();
        if ( errorsField != null ) {
          int index1 = model.logTableFields.indexOf( errorsField );
          try {
            errors = colinf[index1].getValueMeta().getInteger( rowData[index1] );
          } catch ( KettleValueException e ) {
            log.logError( "history data conversion issue", e );
          }
        }
        LogTableField statusField = model.logTable.getStatusField();
        if ( statusField != null ) {
          int index1 = model.logTableFields.indexOf( statusField );
          String statusString = null;
          try {
            statusString = colinf[index1].getValueMeta().getString( rowData[index1] );
          } catch ( KettleValueException e ) {
            log.logError( "history data conversion issue", e );
          }
          if ( statusString != null ) {
            status = LogStatus.findStatus( statusString );
          }
        }

        if ( errors != null && errors > 0L ) {
          item.setBackground( GUIResource.getInstance().getColorRed() );
        } else if ( status != null && LogStatus.STOP.equals( status ) ) {
          item.setBackground( GUIResource.getInstance().getColorYellow() );
        }
      }

      model.logDisplayTableView.removeEmptyRows();
      model.logDisplayTableView.setRowNums();
      model.logDisplayTableView.optWidth( true );
    } else {
      model.logDisplayTableView.clearAll( false );
      // new TableItem(wFields.get(tabIndex).table, SWT.NONE); // Give it an item to prevent errors on various
      // platforms.
    }

    if ( selectionIndex >= 0 && selectionIndex < model.logDisplayTableView.getItemCount() ) {
      model.logDisplayTableView.table.select( selectionIndex );
      showLogEntry();
    }
  }

  private void showLogEntry() {
    JobHistoryLogTab model = models[tabFolder.getSelectionIndex()];

    Text text = model.logDisplayText;

    if ( text == null || text.isDisposed() ) {
      return;
    }

    List<Object[]> list = model.rows;

    if ( list == null || list.size() == 0 ) {
      String message;
      if ( model.logTable.isDefined() ) {
        message = BaseMessages.getString( PKG, "JobHistory.PleaseRefresh.Message" );
      } else {
        message = BaseMessages.getString( PKG, "JobHistory.HistoryConfiguration.Message" );
      }
      text.setText( message );
      return;
    }

    // grab the selected line in the table:
    int nr = model.logDisplayTableView.table.getSelectionIndex();
    if ( nr >= 0 && nr < list.size() ) {
      // OK, grab this one from the buffer...
      Object[] row = list.get( nr );

      // What is the name of the log field?
      //
      LogTableField logField = model.logTable.getLogField();
      if ( logField != null ) {
        int index = model.logTableFields.indexOf( logField );
        if ( index >= 0 ) {
          String logText = row[index].toString();

          text.setText( Const.NVL( logText, "" ) );

          text.setSelection( text.getText().length() );
          text.showSelection();
        } else {
          text.setText( BaseMessages.getString( PKG, "JobHistory.HistoryConfiguration.NoLoggingFieldDefined" ) );
        }
      }
    }
  }

  /**
   * @return the jobHistoryTab
   */
  public CTabItem getJobHistoryTab() {
    return jobHistoryTab;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#getData()
   */
  public Object getData() {
    return null;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#getName()
   */
  public String getName() {
    return "history";
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#getXulDomContainer()
   */
  public XulDomContainer getXulDomContainer() {
    return null;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#setData(java.lang.Object)
   */
  public void setData( Object data ) {
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#setName(java.lang.String)
   */
  public void setName( String name ) {
  }

  /*
   * (non-Javadoc)
   *
   * @see org.pentaho.ui.xul.impl.XulEventHandler#setXulDomContainer(org.pentaho.ui.xul.XulDomContainer)
   */
  public void setXulDomContainer( XulDomContainer xulDomContainer ) {
  }

  /**
   * XUL event: fetches next x records for current log table.
   */
  public void fetchNextBatch() {
    int tabIndex = tabFolder.getSelectionIndex();
    refreshHistory( tabIndex, Mode.NEXT_BATCH );
  }

  /**
   * XUL event: loads all load records for current log table.
   */
  public void fetchAll() {
    int tabIndex = tabFolder.getSelectionIndex();
    refreshHistory( tabIndex, Mode.ALL );
  }

  private class JobHistoryLogTab extends CTabItem {
    private List<LogTableField> logTableFields = new ArrayList<LogTableField>();
    private List<Object[]> rows;
    private LogTableInterface logTable;
    private Text logDisplayText;
    private TableView logDisplayTableView;

    /**
     * Number of batches fetched so far. When the next batch is fetched, the number of rows displayed will be the max of
     * batchCount * BATCH_SIZE and resultSet row count.
     */
    public int batchCount;

    public JobHistoryLogTab( CTabFolder tabFolder, LogTableInterface logTable ) {
      super( tabFolder, SWT.NONE );
      setLogTable( logTable );

      setText( logTable.getLogTableType() );

      Composite logTableComposite = new Composite( tabFolder, SWT.NONE );
      logTableComposite.setLayout( new FormLayout() );
      spoon.props.setLook( logTableComposite );

      setControl( logTableComposite );

      SashForm sash = new SashForm( logTableComposite, SWT.VERTICAL );
      sash.setLayout( new FillLayout() );
      FormData fdSash = new FormData();
      fdSash.left = new FormAttachment( 0, 0 ); // First one in the left top corner
      fdSash.top = new FormAttachment( 0, 0 );
      fdSash.right = new FormAttachment( 100, 0 );
      fdSash.bottom = new FormAttachment( 100, 0 );
      sash.setLayoutData( fdSash );

      logDisplayTableView = createJobLogTableView( sash );

      if ( logTable.getLogField() != null ) {
        logDisplayText = new Text( sash, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.READ_ONLY );
        spoon.props.setLook( logDisplayText );
        logDisplayText.setVisible( true );

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

        sash.setWeights( new int[] { 70, 30, } );
      } else {
        logDisplayText = null;
        sash.setWeights( new int[] { 100, } );
      }
    }

    public void setLogTable( LogTableInterface logTable ) {
      this.logTable = logTable;
      logTableFields.clear();
      for ( LogTableField field : logTable.getFields() ) {
        if ( field.isEnabled() && field.isVisible() ) {
          logTableFields.add( field );
        }
      }

      // Recreate table view as log table has changed
      if ( logDisplayTableView != null ) {
        Composite tableParent = logDisplayTableView.getParent();
        TableView newTable = createJobLogTableView( tableParent );
        newTable.moveAbove( logDisplayTableView );
        logDisplayTableView.dispose();
        tableParent.layout( false );
        logDisplayTableView = newTable;
      }
    }

    private TableView createJobLogTableView( Composite parent ) {
      List<ColumnInfo> columnList = new ArrayList<ColumnInfo>();

      for ( LogTableField field : logTableFields ) {
        if ( !field.isLogField() ) {
          ColumnInfo column = new ColumnInfo( field.getName(), ColumnInfo.COLUMN_TYPE_TEXT, false, true );
          int valueType = field.getDataType();
          String conversionMask = null;

          switch ( field.getDataType() ) {
            case ValueMetaInterface.TYPE_INTEGER:
              conversionMask = "###,###,##0";
              column.setAllignement( SWT.RIGHT );
              break;
            case ValueMetaInterface.TYPE_DATE:
              conversionMask = "yyyy/MM/dd HH:mm:ss";
              column.setAllignement( SWT.CENTER );
              break;
            case ValueMetaInterface.TYPE_NUMBER:
              conversionMask = " ###,###,##0.00;-###,###,##0.00";
              column.setAllignement( SWT.RIGHT );
              break;
            case ValueMetaInterface.TYPE_STRING:
              column.setAllignement( SWT.LEFT );
              break;
            case ValueMetaInterface.TYPE_BOOLEAN:
              DatabaseMeta databaseMeta = logTable.getDatabaseMeta();
              if ( databaseMeta != null ) {
                if ( !databaseMeta.supportsBooleanDataType() ) {
                  // Boolean gets converted to String!
                  //
                  valueType = ValueMetaInterface.TYPE_STRING;
                }
              }
              break;
            default:
              break;
          }

          ValueMetaInterface valueMeta = new ValueMeta( field.getFieldName(), valueType, field.getLength(), -1 );
          if ( conversionMask != null ) {
            valueMeta.setConversionMask( conversionMask );
          }
          column.setValueMeta( valueMeta );
          columnList.add( column );
        }
      }

      TableView tableView = new TableView( jobMeta, parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.SINGLE,
        columnList.toArray( new ColumnInfo[columnList.size()] ), 1,
        true, // readonly!
        null,
        spoon.props );

      tableView.table.addSelectionListener( new SelectionAdapter() {
        public void widgetSelected( SelectionEvent arg0 ) {
          showLogEntry();
        }
      } );

      return tableView;
    }
  }
}
