package com.avaje.ebeaninternal.server.persist;

import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.SpiCallableSql;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql;
import com.avaje.ebeaninternal.server.util.BindParamsParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.PersistenceException;
import java.sql.CallableStatement;
import java.sql.SQLException;

/**
 * Handles the execution of CallableSql requests.
 */
public class ExeCallableSql {

  private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class);

  private final Binder binder;

  private final PstmtFactory pstmtFactory;

  public ExeCallableSql(Binder binder) {
    this.binder = binder;
    this.pstmtFactory = new PstmtFactory();
  }

  /**
   * execute the CallableSql requests.
   */
  public int execute(PersistRequestCallableSql request) {

    boolean batchThisRequest = request.isBatchThisRequest();

    CallableStatement cstmt = null;
    try {
      cstmt = bindStmt(request, batchThisRequest);
      if (batchThisRequest) {
        cstmt.addBatch();
        // return -1 to indicate batch mode
        return -1;
      } else {
        // handles executeOverride() and also
        // reading of registered OUT parameters
        int rowCount = request.executeUpdate();
        request.postExecute();
        return rowCount;
      }

    } catch (SQLException ex) {
      throw new PersistenceException(ex);

    } finally {
      if (!batchThisRequest && cstmt != null) {
        try {
          cstmt.close();
        } catch (SQLException e) {
          logger.error(null, e);
        }
      }
    }
  }


  private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {

    SpiCallableSql callableSql = request.getCallableSql();
    SpiTransaction t = request.getTransaction();

    String sql = callableSql.getSql();

    BindParams bindParams = callableSql.getBindParams();

    // process named parameters if required
    sql = BindParamsParser.parse(bindParams, sql);

    boolean logSql = request.isLogSql();

    CallableStatement cstmt;
    if (batchThisRequest) {
      cstmt = pstmtFactory.getCstmt(t, logSql, sql, request);
    } else {
      if (logSql) {
        t.logSql(sql);
      }
      cstmt = pstmtFactory.getCstmt(t, sql);
    }

    if (callableSql.getTimeout() > 0) {
      cstmt.setQueryTimeout(callableSql.getTimeout());
    }

    String bindLog = null;
    if (!bindParams.isEmpty()) {
      bindLog = binder.bind(bindParams, cstmt, t.getInternalConnection());
    }

    request.setBindLog(bindLog);

    // required to read OUT params later
    request.setBound(bindParams, cstmt);
    return cstmt;
  }
}
