/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2019 by Hitachi Vantara : 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.repository.kdr.delegates;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.pentaho.di.cluster.SlaveServer;
import org.pentaho.di.core.RowMetaAndData;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleDatabaseException;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.plugins.JobEntryPluginType;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaBoolean;
import org.pentaho.di.core.row.value.ValueMetaInteger;
import org.pentaho.di.core.row.value.ValueMetaString;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.job.entries.missing.MissingEntry;
import org.pentaho.di.job.entry.JobEntryBase;
import org.pentaho.di.job.entry.JobEntryCopy;
import org.pentaho.di.job.entry.JobEntryInterface;
import org.pentaho.di.repository.LongObjectId;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.kdr.KettleDatabaseRepository;
import org.pentaho.di.repository.kdr.delegates.metastore.KettleDatabaseRepositoryMetaStore;

public class KettleDatabaseRepositoryJobEntryDelegate extends KettleDatabaseRepositoryBaseDelegate {
  // private static Class<?> PKG = JobEntryCopy.class; // for i18n purposes, needed by Translator2!!

  public static final String JOBENTRY_ATTRIBUTE_PREFIX = "_ATTR_" + '\t';

  public KettleDatabaseRepositoryJobEntryDelegate( KettleDatabaseRepository repository ) {
    super( repository );
  }

  public RowMetaAndData getJobEntry( ObjectId id_jobentry ) throws KettleException {
    return repository.connectionDelegate.getOneRow(
      quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOBENTRY ), id_jobentry );
  }

  public RowMetaAndData getJobEntryCopy( ObjectId id_jobentry_copy ) throws KettleException {
    return repository.connectionDelegate.getOneRow(
      quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY_COPY ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY_COPY ), id_jobentry_copy );
  }

  public RowMetaAndData getJobEntryType( ObjectId id_jobentry_type ) throws KettleException {
    return repository.connectionDelegate.getOneRow(
      quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY_TYPE ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOBENTRY_TYPE ), id_jobentry_type );
  }

  public synchronized ObjectId getJobEntryID( String name, ObjectId id_job ) throws KettleException {
    return repository.connectionDelegate.getIDWithValue(
      quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOBENTRY ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_NAME ), name,
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOB ), id_job );
  }

  public synchronized ObjectId getJobEntryTypeID( String code ) throws KettleException {
    return repository.connectionDelegate.getIDWithValue(
      quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY_TYPE ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_TYPE_ID_JOBENTRY_TYPE ),
      quote( KettleDatabaseRepository.FIELD_JOBENTRY_TYPE_CODE ), code );
  }

  /**
   * Load the chef graphical entry from repository We load type, name & description if no entry can be found.
   *
   * @param log
   *          the logging channel
   * @param rep
   *          the Repository
   * @param jobId
   *          The job ID
   * @param jobEntryCopyId
   *          The jobentry copy ID
   * @param jobentries
   *          A list with all jobentries
   * @param databases
   *          A list with all defined databases
   */
  public JobEntryCopy loadJobEntryCopy( ObjectId jobId, ObjectId jobEntryCopyId,
    List<JobEntryInterface> jobentries, List<DatabaseMeta> databases, List<SlaveServer> slaveServers, String jobname )
        throws KettleException {
    JobEntryCopy jobEntryCopy = new JobEntryCopy();

    try {
      jobEntryCopy.setObjectId( jobEntryCopyId );

      // Handle GUI information: nr, location, ...
      RowMetaAndData r = getJobEntryCopy( jobEntryCopyId );
      if ( r != null ) {
        // These are the jobentry_copy fields...
        //
        ObjectId jobEntryId =
          new LongObjectId( r.getInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY, 0 ) );
        ObjectId jobEntryTypeId =
          new LongObjectId( r.getInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY_TYPE, 0 ) );
        jobEntryCopy.setNr( (int) r.getInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_NR, 0 ) );
        int locx = (int) r.getInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_LOCATION_X, 0 );
        int locy = (int) r.getInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_LOCATION_Y, 0 );
        boolean isdrawn = r.getBoolean( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_DRAW, false );
        boolean isparallel = r.getBoolean( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_PARALLEL, false );

        // Do we have the jobentry already?
        //
        jobEntryCopy.setEntry( JobMeta.findJobEntry( jobentries, jobEntryId ) );
        if ( jobEntryCopy.getEntry() == null ) {
          // What type of jobentry do we load now?
          // Get the jobentry type code
          //
          RowMetaAndData rt = getJobEntryType( new LongObjectId( jobEntryTypeId ) );
          if ( rt != null ) {
            String jet_code = rt.getString( KettleDatabaseRepository.FIELD_JOBENTRY_TYPE_CODE, null );
            JobEntryInterface jobEntry = null;
            PluginRegistry registry = PluginRegistry.getInstance();
            PluginInterface jobPlugin = registry.findPluginWithId( JobEntryPluginType.class, jet_code );
            if ( jobPlugin == null ) {
              jobEntry = new MissingEntry( jobname, jet_code );
            } else {
              jobEntry = (JobEntryInterface) registry.loadClass( jobPlugin );
            }
            if ( jobEntry != null ) {
              jobEntryCopy.setEntry( jobEntry );
              // Load the attributes for that jobentry
              //
              // THIS IS THE PLUGIN/JOB-ENTRY BEING LOADED!
              //

              // If you extended the JobEntryBase class, you're fine.
              // Otherwise you're on your own.
              //
              if ( jobEntry instanceof JobEntryBase ) {
                loadJobEntryBase( (JobEntryBase) jobEntry, jobEntryId, databases, slaveServers );
                ( (JobEntryBase) jobEntry ).setAttributesMap( loadJobEntryAttributesMap( jobId, jobEntryId ) );
              }

              compatibleJobEntryLoadRep( jobEntry, repository, jobEntryTypeId, databases, slaveServers );
              jobEntry.loadRep( repository, repository.metaStore, jobEntryId, databases, slaveServers );

              jobEntryCopy.getEntry().setObjectId( jobEntryId );

              jobentries.add( jobEntryCopy.getEntry() );
            } else {
              throw new KettleException( "JobEntryLoader was unable to find Job Entry Plugin with description ["
                + jet_code + "]." );
            }
          } else {
            throw new KettleException( "Unable to find Job Entry Type with id="
              + jobEntryTypeId + " in the repository" );
          }
        }

        jobEntryCopy.setLocation( locx, locy );
        jobEntryCopy.setDrawn( isdrawn );
        jobEntryCopy.setLaunchingInParallel( isparallel );

        return jobEntryCopy;
      } else {
        throw new KettleException( "Unable to find job entry copy in repository with id_jobentry_copy="
          + jobEntryCopyId );
      }
    } catch ( KettleDatabaseException dbe ) {
      throw new KettleException( "Unable to load job entry copy from repository with id_jobentry_copy="
        + jobEntryCopyId, dbe );
    }
  }

  @SuppressWarnings( "deprecation" )
  private void compatibleJobEntryLoadRep( JobEntryInterface jobEntry, KettleDatabaseRepository repository,
    ObjectId id_jobentry_type, List<DatabaseMeta> databases, List<SlaveServer> slaveServers ) throws KettleException {

    jobEntry.loadRep( repository, id_jobentry_type, databases, slaveServers );

  }

  public void saveJobEntryCopy( JobEntryCopy copy, ObjectId id_job, KettleDatabaseRepositoryMetaStore metaStore ) throws KettleException {
    try {
      JobEntryInterface entry = copy.getEntry();
      /*
       * --1-- Save the JobEntryCopy details... --2-- If we don't find a id_jobentry, save the jobentry (meaning: only
       * once)
       */

      // See if an entry with the same name is already available...
      ObjectId id_jobentry = getJobEntryID( copy.getName(), id_job );
      if ( id_jobentry == null ) {
        insertJobEntry( id_job, (JobEntryBase) entry );

        // THIS IS THE PLUGIN/JOB-ENTRY BEING SAVED!
        //
        entry.saveRep( repository, metaStore, id_job );
        compatibleEntrySaveRep( entry, repository, id_job );

        // Save the attribute groups map
        //
        if ( entry instanceof JobEntryBase ) {
          saveAttributesMap( id_job, copy.getObjectId(), ( (JobEntryBase) entry ).getAttributesMap() );
        }

        id_jobentry = entry.getObjectId();
      }

      // OK, the entry is saved.
      // Get the entry type...
      //
      ObjectId id_jobentry_type = getJobEntryTypeID( entry.getPluginId() );

      // Oops, not found: update the repository!
      if ( id_jobentry_type == null ) {
        repository.updateJobEntryTypes();

        // Try again!
        id_jobentry_type = getJobEntryTypeID( entry.getPluginId() );
      }

      // Save the entry copy..
      //
      copy.setObjectId( insertJobEntryCopy( id_job, id_jobentry, id_jobentry_type, copy.getNr(), copy
        .getLocation().x, copy.getLocation().y, copy.isDrawn(), copy.isLaunchingInParallel() ) );

    } catch ( KettleDatabaseException dbe ) {
      throw new KettleException( "Unable to save job entry copy to the repository, id_job=" + id_job, dbe );
    }
  }

  @SuppressWarnings( "deprecation" )
  private void compatibleEntrySaveRep( JobEntryInterface entry, Repository repository, ObjectId id_job ) throws KettleException {
    entry.saveRep( repository, id_job );
  }

  public synchronized ObjectId insertJobEntry( ObjectId id_job, JobEntryBase jobEntryBase ) throws KettleException {
    ObjectId id = repository.connectionDelegate.getNextJobEntryID();

    ObjectId id_jobentry_type = getJobEntryTypeID( jobEntryBase.getPluginId() );

    log.logDebug( "ID_JobEntry_type = " + id_jobentry_type + " for type = [" + jobEntryBase.getPluginId() + "]" );

    RowMetaAndData table = new RowMetaAndData();

    table.addValue( new ValueMetaInteger(
      KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOBENTRY ), id );
    table.addValue(
      new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOB ), id_job );
    table
      .addValue(
        new ValueMetaInteger(
          KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOBENTRY_TYPE ),
        id_jobentry_type );
    table.addValue(
      new ValueMetaString( KettleDatabaseRepository.FIELD_JOBENTRY_NAME ),
      jobEntryBase.getName() );
    table.addValue( new ValueMetaString(
      KettleDatabaseRepository.FIELD_JOBENTRY_DESCRIPTION ), jobEntryBase
      .getDescription() );

    repository.connectionDelegate.getDatabase().prepareInsert(
      table.getRowMeta(), KettleDatabaseRepository.TABLE_R_JOBENTRY );
    repository.connectionDelegate.getDatabase().setValuesInsert( table );
    repository.connectionDelegate.getDatabase().insertRow();
    repository.connectionDelegate.getDatabase().closeInsert();

    jobEntryBase.setObjectId( id );

    return id;
  }

  public synchronized ObjectId insertJobEntryCopy( ObjectId id_job, ObjectId id_jobentry,
    ObjectId id_jobentry_type, int nr, long gui_location_x, long gui_location_y, boolean gui_draw,
    boolean parallel ) throws KettleException {
    ObjectId id = repository.connectionDelegate.getNextJobEntryCopyID();

    RowMetaAndData table = new RowMetaAndData();

    //CHECKSTYLE:LineLength:OFF
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY_COPY ), id );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY ), id_jobentry );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOB ), id_job );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_ID_JOBENTRY_TYPE ), id_jobentry_type );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_NR ), new Long( nr ) );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_LOCATION_X ), new Long( gui_location_x ) );
    table.addValue( new ValueMetaInteger( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_LOCATION_Y ), new Long( gui_location_y ) );
    table.addValue( new ValueMetaBoolean( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_GUI_DRAW ), Boolean.valueOf( gui_draw ) );
    table.addValue( new ValueMetaBoolean( KettleDatabaseRepository.FIELD_JOBENTRY_COPY_PARALLEL ), Boolean.valueOf( parallel ) );

    repository.connectionDelegate.getDatabase().prepareInsert( table.getRowMeta(), KettleDatabaseRepository.TABLE_R_JOBENTRY_COPY );
    repository.connectionDelegate.getDatabase().setValuesInsert( table );
    repository.connectionDelegate.getDatabase().insertRow();
    repository.connectionDelegate.getDatabase().closeInsert();

    return id;
  }

  public synchronized int getNrJobEntries( ObjectId id_job ) throws KettleException {
    int retval = 0;

    RowMetaAndData par = repository.connectionDelegate.getParameterMetaData( id_job );
    String sql =
      "SELECT COUNT(*) FROM "
        + quoteTable( KettleDatabaseRepository.TABLE_R_JOBENTRY ) + " WHERE "
        + quote( KettleDatabaseRepository.FIELD_JOBENTRY_ID_JOB ) + " = ? ";
    RowMetaAndData r = repository.connectionDelegate.getOneRow( sql, par.getRowMeta(), par.getData() );
    if ( r != null ) {
      retval = (int) r.getInteger( 0, 0L );
    }

    return retval;
  }

  public void loadJobEntryBase( JobEntryBase jobEntryBase, ObjectId id_jobentry, List<DatabaseMeta> databases,
    List<SlaveServer> slaveServers ) throws KettleException {
    try {
      RowMetaAndData r = getJobEntry( id_jobentry );
      if ( r != null ) {
        jobEntryBase.setName( r.getString( "NAME", null ) );

        jobEntryBase.setDescription( r.getString( "DESCRIPTION", null ) );
        long id_jobentry_type = r.getInteger( "ID_JOBENTRY_TYPE", 0 );
        RowMetaAndData jetrow = getJobEntryType( new LongObjectId( id_jobentry_type ) );
        if ( jetrow != null ) {
          jobEntryBase.setPluginId( jetrow.getString( "CODE", null ) );
        }
      }
    } catch ( KettleDatabaseException dbe ) {
      throw new KettleException( "Unable to load base job entry information from the repository for id_jobentry="
        + id_jobentry, dbe );
    }
  }

  private void saveAttributesMap( ObjectId jobId, ObjectId entryId, Map<String, Map<String, String>> attributesMap ) throws KettleException {
    for ( final Map.Entry<String, Map<String, String>> attributesEntry : attributesMap.entrySet() ) {
      Map<String, String> attributes = attributesEntry.getValue();
      String groupName = attributesEntry.getKey();
      for ( final Map.Entry<String, String> entry : attributes.entrySet() ) {
        final String value = entry.getValue();
        final String key = entry.getKey();
        if ( key != null && value != null ) {
          repository.connectionDelegate.insertJobEntryAttribute( jobId, entryId, 0, JOBENTRY_ATTRIBUTE_PREFIX
            + groupName + '\t' + value, 0, value );
        }
      }
    }
  }

  private Map<String, Map<String, String>> loadJobEntryAttributesMap( ObjectId jobId, Object jobEntryId ) throws KettleException {
    Map<String, Map<String, String>> attributesMap = new HashMap<String, Map<String, String>>();

    List<Object[]> attributeRows =
      repository.connectionDelegate.getJobEntryAttributesWithPrefix( jobId, jobId, JOBENTRY_ATTRIBUTE_PREFIX );
    RowMetaInterface rowMeta = repository.connectionDelegate.getReturnRowMeta();
    for ( Object[] attributeRow : attributeRows ) {
      String code = rowMeta.getString( attributeRow, KettleDatabaseRepository.FIELD_JOBENTRY_ATTRIBUTE_CODE, null );
      String value =
        rowMeta.getString( attributeRow, KettleDatabaseRepository.FIELD_JOBENTRY_ATTRIBUTE_VALUE_STR, null );
      if ( code != null && value != null ) {
        code = code.substring( JOBENTRY_ATTRIBUTE_PREFIX.length() );
        int tabIndex = code.indexOf( '\t' );
        if ( tabIndex > 0 ) {
          String groupName = code.substring( 0, tabIndex );
          String key = code.substring( tabIndex + 1 );
          Map<String, String> attributes = attributesMap.get( groupName );
          if ( attributes == null ) {
            attributes = new HashMap<String, String>();
            attributesMap.put( groupName, attributes );
          }
          attributes.put( key, value );
        }
      }
    }

    return attributesMap;
  }
}
