/*
 * Copyright (c) 2013 by Stefan Ferstl <st.ferstl@gmail.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 com.github.ferstl.spring.jdbc.oracle;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.InterruptibleBatchPreparedStatementSetter;
import org.springframework.jdbc.core.PreparedStatementCallback;

import oracle.jdbc.OraclePreparedStatement;

class BatchingPreparedStatementCallback implements PreparedStatementCallback<int[]> {

  private final int sendBatchSize;
  private final BatchPreparedStatementSetter pss;

  BatchingPreparedStatementCallback(int sendBatchSize, BatchPreparedStatementSetter pss) {
    this.sendBatchSize = sendBatchSize;
    this.pss = pss;
  }

  @Override
  public int[] doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
    OraclePreparedStatement ops = (OraclePreparedStatement) ps;
    int batchSize = this.pss.getBatchSize();

    // Don't use an int[] array here because instances of InterruptibleBatchPreparedStatementSetter
    // might return Integer.MAX_VALUE as batch size.
    List<Integer> rowCounts = new ArrayList<>();

    if (this.pss instanceof InterruptibleBatchPreparedStatementSetter) {
      InterruptibleBatchPreparedStatementSetter ipss = (InterruptibleBatchPreparedStatementSetter) this.pss;
      executeUpdate(ops, ipss, rowCounts);
    } else {
      int completeBatchSize = (batchSize / this.sendBatchSize) * this.sendBatchSize;
      int remainingBatchSize = batchSize % this.sendBatchSize;
      executeUpdate(ops, rowCounts, 0, completeBatchSize);
      executeUpdate(ops, rowCounts, completeBatchSize, completeBatchSize + remainingBatchSize);
    }

    return toIntArray(rowCounts);
  }

  private void executeUpdate(OraclePreparedStatement ops, List<Integer> rowCounts, int start, int end)
  throws SQLException {

    int batchSize = end - start;

    if (batchSize > 0) {
      int sendBatchSize = this.sendBatchSize < batchSize ? this.sendBatchSize : batchSize;
      ops.setExecuteBatch(sendBatchSize);

      for (int i = start; i < end; i++) {
        this.pss.setValues(ops, i);
        rowCounts.add(ops.executeUpdate());
      }
    }
  }

  private void executeUpdate(OraclePreparedStatement ops, InterruptibleBatchPreparedStatementSetter ipss, List<Integer> rowCounts)
  throws SQLException {

    ops.setExecuteBatch(this.sendBatchSize);
    int i = 0;
    while (i < ipss.getBatchSize()) {
      ipss.setValues(ops, i);
      if (ipss.isBatchExhausted(i)) {
        break;
      }
      rowCounts.add(ops.executeUpdate());
      i++;
    }

    if (i > 0 && i % this.sendBatchSize != 0) {
      rowCounts.set(rowCounts.size() - 1, ops.sendBatch());
    }

  }

  private static int[] toIntArray(List<Integer> intList) {
    int[] array = new int[intList.size()];
    int i = 0;
    for (Integer integer : intList) {
      array[i++] = integer;
    }
    return array;
  }
}

@@ -2096,17 +2096,23 @@
     int 
-c
+sizeOfC
 ompleteB
@@ -2115,20 +2115,18 @@
 eteBatch
-Siz
 e
+s
  = (batc
@@ -2185,34 +2185,31 @@
     int 
-remaining
+sizeOfLast
 Batch
-Size
  = batch
@@ -2274,17 +2274,23 @@
 nts, 0, 
-c
+sizeOfC
 ompleteB
@@ -2289,28 +2289,26 @@
 ompleteBatch
-Siz
 e
+s
 );%0A      exe
@@ -2334,17 +2334,23 @@
 Counts, 
-c
+sizeOfC
 ompleteB
@@ -2357,15 +2357,19 @@
 atch
-Size, c
+es, sizeOfC
 ompl
@@ -2380,33 +2380,28 @@
 atch
-Size + remaining
+es + sizeOfLast
 Batch
-Size
 );%0A 

Rename variables