package org.jetbrains.exposed.sql.statements.jdbc

import org.jetbrains.exposed.sql.BinaryColumnType
import org.jetbrains.exposed.sql.BlobColumnType
import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.statements.StatementResult
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import org.jetbrains.exposed.sql.vendors.SQLiteDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
import java.io.InputStream
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Statement
import java.sql.Types

/**
 * Class representing a precompiled SQL [statement].
 *
 * The result set generated by executing this statement contains auto-generated keys based on the value of
 * [wasGeneratedKeysRequested].
 */
class JdbcPreparedStatementImpl(
    val statement: PreparedStatement,
    val wasGeneratedKeysRequested: Boolean
) : PreparedStatementApi {
    override val resultSet: ResultSet?
        get() = when {
            !wasGeneratedKeysRequested -> statement.resultSet
            currentDialect is SQLiteDialect -> {
                statement.connection.prepareStatement("select last_insert_rowid();").executeQuery()
            }
            else -> statement.generatedKeys
        }

    override var fetchSize: Int?
        get() = statement.fetchSize
        set(value) {
            value?.let { statement.fetchSize = value }
        }

    override var timeout: Int?
        get() = statement.queryTimeout
        set(value) {
            value?.let { statement.queryTimeout = it }
        }

    override fun addBatch() {
        statement.addBatch()
    }

    override fun executeQuery(): ResultSet = statement.executeQuery()

    override fun executeUpdate(): Int = statement.executeUpdate()

    override fun executeMultiple(): List<StatementResult> {
        // execute() returns true only if first result is a ResultSet
        return if (statement.execute()) {
            listOf(StatementResult.Object(statement.resultSet))
        } else {
            // getMoreResults() returns true only if next result is a ResultSet
            while (!statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT)) {
                if (statement.updateCount == -1) return emptyList()
            }
            listOf(StatementResult.Object(statement.resultSet))
        }
    }

    override fun set(index: Int, value: Any) {
        statement.setObject(index, value)
    }

    override fun setNull(index: Int, columnType: IColumnType<*>) {
        if (columnType is BinaryColumnType || (columnType is BlobColumnType && !columnType.useObjectIdentifier)) {
            statement.setNull(index, Types.LONGVARBINARY)
        } else {
            statement.setObject(index, null)
        }
    }

    override fun setInputStream(index: Int, inputStream: InputStream, setAsBlobObject: Boolean) {
        if (setAsBlobObject) {
            statement.setBlob(index, inputStream)
        } else {
            statement.setBinaryStream(index, inputStream, inputStream.available())
        }
    }

    override fun setArray(index: Int, type: String, array: Array<*>) {
        statement.setArray(index, statement.connection.createArrayOf(type, array))
    }

    override fun closeIfPossible() {
        if (!statement.isClosed) statement.close()
    }

    override fun executeBatch(): List<Int> {
        return statement.executeBatch().map {
            when (it) {
                Statement.SUCCESS_NO_INFO -> 1
                Statement.EXECUTE_FAILED -> 0
                else -> it
            }
        }
    }

    override fun cancel() {
        if (!statement.isClosed) statement.cancel()
    }
}
