/*
 * Copyright (c) 2011-2014 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.ext.jdbc.impl.actions;

import io.vertx.core.json.JsonArray;
import io.vertx.ext.sql.SQLOptions;
import io.vertx.ext.sql.UpdateResult;
import io.vertx.ext.jdbc.spi.JDBCColumnDescriptorProvider;

import java.sql.*;

/**
 * @author <a href="mailto:nscavell@redhat.com">Nick Scavelli</a>
 * @author <a href="mailto:plopes@redhat.com">Paulo Lopes</a>
 */
public class JDBCUpdate extends AbstractJDBCAction<UpdateResult> {

  private final String sql;
  private final JsonArray in;

  private boolean generateKeys;

  public JDBCUpdate(JDBCStatementHelper helper, SQLOptions options, String sql, JsonArray in) {
    super(helper, options);
    this.sql = sql;
    this.in = in;
  }

  private PreparedStatement prepareStatement(Connection conn) throws SQLException {
    if (options == null) {
      generateKeys = true;
      return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    }

    if (!options.isAutoGeneratedKeys()) {
      generateKeys = false;
      return conn.prepareStatement(sql, Statement.NO_GENERATED_KEYS);
    }

    if (options.getAutoGeneratedKeysIndexes() == null) {
      generateKeys = true;
      return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    }

    if (options.getAutoGeneratedKeysIndexes().getValue(0) instanceof String) {
      String[] columnNames = new String[options.getAutoGeneratedKeysIndexes().size()];
      for (int i = 0; i < columnNames.length; i++) {
        columnNames[i] = options.getAutoGeneratedKeysIndexes().getString(i);
      }

      generateKeys = true;
      return conn.prepareStatement(sql, columnNames);
    }

    if (options.getAutoGeneratedKeysIndexes().getValue(0) instanceof Number) {
      int[] columnIndexes = new int[options.getAutoGeneratedKeysIndexes().size()];
      for (int i = 0; i < columnIndexes.length; i++) {
        columnIndexes[i] = options.getAutoGeneratedKeysIndexes().getInteger(i);
      }

      generateKeys = true;
      return conn.prepareStatement(sql, columnIndexes);
    }

    // invalid type
    throw new SQLException("Invalid type for auto generated keys");
  }

  @Override
  public UpdateResult execute(Connection conn) throws SQLException {
    try (PreparedStatement statement = prepareStatement(conn)) {
      // apply statement options
      applyStatementOptions(statement);

      fillStatement(statement, in);

      int updated = statement.executeUpdate();
      JsonArray keys = new JsonArray();

      // Create JsonArray of keys
      if (generateKeys) {
        ResultSet rs = null;
        try {
          // the resource might also fail
          // specially on oracle DBMS
          rs = statement.getGeneratedKeys();
          if (rs != null) {
            final JDBCColumnDescriptorProvider provider = JDBCColumnDescriptorProvider.fromResult(rs);
            final ResultSetMetaData metaData = rs.getMetaData();
            final int columns = metaData != null ? metaData.getColumnCount() : 1;
            while (rs.next()) {
              for (int i = 1; i <= columns; i++) {
                final Object key = helper.getDecoder().parse(rs, i, provider);
                if (key == null) {
                  keys.addNull();
                } else {
                  keys.add(key);
                }
              }
            }
          }
        } catch (SQLException e) {
          // do not crash if no permissions
        } finally {
          if (rs != null) {
            try {
              rs.close();
            } catch (SQLException e) {
              // ignore close error
            }
          }
        }
      }

      return new UpdateResult(updated, keys);
    }
  }

}
