package xyz.nulldev.androidcompat.db

import java.io.InputStream
import java.io.Reader
import java.math.BigDecimal
import java.net.URL
import java.sql.Array
import java.sql.Blob
import java.sql.Clob
import java.sql.Date
import java.sql.NClob
import java.sql.Ref
import java.sql.ResultSet
import java.sql.ResultSetMetaData
import java.sql.RowId
import java.sql.SQLXML
import java.sql.Time
import java.sql.Timestamp
import java.util.Calendar

@Suppress("UNCHECKED_CAST")
class ScrollableResultSet(
    val parent: ResultSet,
) : ResultSet by parent {
    private val cachedContent = mutableListOf<ResultSetEntry>()
    private val columnCache = mutableMapOf<String, Int>()
    private var lastReturnWasNull = false
    private var cursor = 0
    var resultSetLength = 0

    val parentMetadata = parent.metaData
    val columnCount = parentMetadata.columnCount
    val columnLabels =
        (1..columnCount)
            .map {
                parentMetadata.getColumnLabel(it)
            }.toTypedArray()

    init {
        val columnCount = columnCount

        // TODO
        // Profiling reveals that this is a bottleneck (average ms for this call is: 48ms)
        // How can we optimize this?
        // We need to fill the cache as the set is loaded

        // Fill cache
        while (parent.next()) {
            cachedContent +=
                ResultSetEntry().apply {
                    for (i in 1..columnCount) {
                        data += parent.getObject(i)
                    }
                }
            resultSetLength++
        }
    }

    private fun notImplemented(): Nothing = throw UnsupportedOperationException("This class currently does not support this operation!")

    private fun cursorValid(): Boolean = isAfterLast || isBeforeFirst

    private fun internalMove(row: Int) {
        if (cursor < 0) {
            cursor = 0
        } else if (cursor > resultSetLength + 1) {
            cursor = resultSetLength + 1
        } else {
            cursor = row
        }
    }

    private fun obj(column: Int): Any? {
        val obj = cachedContent[cursor - 1].data[column - 1]
        lastReturnWasNull = obj == null
        return obj
    }

    private fun obj(column: String?): Any? = obj(cachedFindColumn(column))

    private fun cachedFindColumn(column: String?) =
        columnCache.getOrPut(column!!, {
            findColumn(column)
        })

    override fun getNClob(columnIndex: Int): NClob = obj(columnIndex) as NClob

    override fun getNClob(columnLabel: String?): NClob = obj(columnLabel) as NClob

    override fun updateNString(
        columnIndex: Int,
        nString: String?,
    ) {
        notImplemented()
    }

    override fun updateNString(
        columnLabel: String?,
        nString: String?,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnIndex: Int,
        x: InputStream?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnLabel: String?,
        x: InputStream?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnIndex: Int,
        x: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnLabel: String?,
        x: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnIndex: Int,
        x: InputStream?,
    ) {
        notImplemented()
    }

    override fun updateBinaryStream(
        columnLabel: String?,
        x: InputStream?,
    ) {
        notImplemented()
    }

    override fun updateTimestamp(
        columnIndex: Int,
        x: Timestamp?,
    ) {
        notImplemented()
    }

    override fun updateTimestamp(
        columnLabel: String?,
        x: Timestamp?,
    ) {
        notImplemented()
    }

    override fun updateNCharacterStream(
        columnIndex: Int,
        x: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateNCharacterStream(
        columnLabel: String?,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateNCharacterStream(
        columnIndex: Int,
        x: Reader?,
    ) {
        notImplemented()
    }

    override fun updateNCharacterStream(
        columnLabel: String?,
        reader: Reader?,
    ) {
        notImplemented()
    }

    override fun updateInt(
        columnIndex: Int,
        x: Int,
    ) {
        notImplemented()
    }

    override fun updateInt(
        columnLabel: String?,
        x: Int,
    ) {
        notImplemented()
    }

    override fun moveToInsertRow() {
        notImplemented()
    }

    override fun getDate(columnIndex: Int): Date {
        // TODO Maybe?
        notImplemented()
    }

    override fun getDate(columnLabel: String?): Date {
        // TODO Maybe?
        notImplemented()
    }

    override fun getDate(
        columnIndex: Int,
        cal: Calendar?,
    ): Date {
        // TODO Maybe?
        notImplemented()
    }

    override fun getDate(
        columnLabel: String?,
        cal: Calendar?,
    ): Date {
        // TODO Maybe?
        notImplemented()
    }

    override fun beforeFirst() {
        // TODO Maybe?
        notImplemented()
    }

    override fun updateFloat(
        columnIndex: Int,
        x: Float,
    ) {
        notImplemented()
    }

    override fun updateFloat(
        columnLabel: String?,
        x: Float,
    ) {
        notImplemented()
    }

    override fun getBoolean(columnIndex: Int): Boolean = obj(columnIndex) as Boolean

    override fun getBoolean(columnLabel: String?): Boolean = obj(columnLabel) as Boolean

    override fun isFirst(): Boolean = cursor - 1 < resultSetLength

    override fun getBigDecimal(
        columnIndex: Int,
        scale: Int,
    ): BigDecimal {
        // TODO Maybe?
        notImplemented()
    }

    override fun getBigDecimal(
        columnLabel: String?,
        scale: Int,
    ): BigDecimal {
        // TODO Maybe?
        notImplemented()
    }

    override fun getBigDecimal(columnIndex: Int): BigDecimal = obj(columnIndex) as BigDecimal

    override fun getBigDecimal(columnLabel: String?): BigDecimal = obj(columnLabel) as BigDecimal

    override fun updateBytes(
        columnIndex: Int,
        x: ByteArray?,
    ) {
        notImplemented()
    }

    override fun updateBytes(
        columnLabel: String?,
        x: ByteArray?,
    ) {
        notImplemented()
    }

    override fun isLast(): Boolean = cursor == resultSetLength

    override fun insertRow() {
        notImplemented()
    }

    override fun getTime(columnIndex: Int): Time {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTime(columnLabel: String?): Time {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTime(
        columnIndex: Int,
        cal: Calendar?,
    ): Time {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTime(
        columnLabel: String?,
        cal: Calendar?,
    ): Time {
        // TODO Maybe?
        notImplemented()
    }

    override fun rowDeleted() = false

    override fun last(): Boolean {
        internalMove(resultSetLength)
        return cursorValid()
    }

    override fun isAfterLast(): Boolean = cursor > resultSetLength

    override fun relative(rows: Int): Boolean {
        internalMove(cursor + rows)
        return cursorValid()
    }

    override fun absolute(row: Int): Boolean {
        if (row > 0) {
            internalMove(row)
        } else {
            last()
            for (i in 1..row) {
                previous()
            }
        }
        return cursorValid()
    }

    override fun getSQLXML(columnIndex: Int): SQLXML? {
        // TODO Maybe?
        notImplemented()
    }

    override fun getSQLXML(columnLabel: String?): SQLXML? {
        // TODO Maybe?
        notImplemented()
    }

    override fun <T : Any?> unwrap(iface: Class<T>?): T {
        if (thisIsWrapperFor(iface)) {
            return this as T
        } else {
            return parent.unwrap(iface)
        }
    }

    override fun next(): Boolean {
        internalMove(cursor + 1)
        return cursorValid()
    }

    override fun getFloat(columnIndex: Int): Float = obj(columnIndex) as Float

    override fun getFloat(columnLabel: String?): Float = obj(columnLabel) as Float

    override fun wasNull() = lastReturnWasNull

    override fun getRow(): Int = cursor

    override fun first(): Boolean {
        internalMove(1)
        return cursorValid()
    }

    override fun updateAsciiStream(
        columnIndex: Int,
        x: InputStream?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateAsciiStream(
        columnLabel: String?,
        x: InputStream?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateAsciiStream(
        columnIndex: Int,
        x: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateAsciiStream(
        columnLabel: String?,
        x: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateAsciiStream(
        columnIndex: Int,
        x: InputStream?,
    ) {
        notImplemented()
    }

    override fun updateAsciiStream(
        columnLabel: String?,
        x: InputStream?,
    ) {
        notImplemented()
    }

    override fun getURL(columnIndex: Int): URL = obj(columnIndex) as URL

    override fun getURL(columnLabel: String?): URL = obj(columnLabel) as URL

    override fun updateShort(
        columnIndex: Int,
        x: Short,
    ) {
        notImplemented()
    }

    override fun updateShort(
        columnLabel: String?,
        x: Short,
    ) {
        notImplemented()
    }

    override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE

    override fun updateNClob(
        columnIndex: Int,
        nClob: NClob?,
    ) {
        notImplemented()
    }

    override fun updateNClob(
        columnLabel: String?,
        nClob: NClob?,
    ) {
        notImplemented()
    }

    override fun updateNClob(
        columnIndex: Int,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateNClob(
        columnLabel: String?,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateNClob(
        columnIndex: Int,
        reader: Reader?,
    ) {
        notImplemented()
    }

    override fun updateNClob(
        columnLabel: String?,
        reader: Reader?,
    ) {
        notImplemented()
    }

    override fun updateRef(
        columnIndex: Int,
        x: Ref?,
    ) {
        notImplemented()
    }

    override fun updateRef(
        columnLabel: String?,
        x: Ref?,
    ) {
        notImplemented()
    }

    override fun updateObject(
        columnIndex: Int,
        x: Any?,
        scaleOrLength: Int,
    ) {
        notImplemented()
    }

    override fun updateObject(
        columnIndex: Int,
        x: Any?,
    ) {
        notImplemented()
    }

    override fun updateObject(
        columnLabel: String?,
        x: Any?,
        scaleOrLength: Int,
    ) {
        notImplemented()
    }

    override fun updateObject(
        columnLabel: String?,
        x: Any?,
    ) {
        notImplemented()
    }

    override fun afterLast() {
        internalMove(resultSetLength + 1)
    }

    override fun updateLong(
        columnIndex: Int,
        x: Long,
    ) {
        notImplemented()
    }

    override fun updateLong(
        columnLabel: String?,
        x: Long,
    ) {
        notImplemented()
    }

    override fun getBlob(columnIndex: Int): Blob {
        // TODO Maybe?
        notImplemented()
    }

    override fun getBlob(columnLabel: String?): Blob {
        // TODO Maybe?
        notImplemented()
    }

    override fun updateClob(
        columnIndex: Int,
        x: Clob?,
    ) {
        notImplemented()
    }

    override fun updateClob(
        columnLabel: String?,
        x: Clob?,
    ) {
        notImplemented()
    }

    override fun updateClob(
        columnIndex: Int,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateClob(
        columnLabel: String?,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateClob(
        columnIndex: Int,
        reader: Reader?,
    ) {
        notImplemented()
    }

    override fun updateClob(
        columnLabel: String?,
        reader: Reader?,
    ) {
        notImplemented()
    }

    override fun getByte(columnIndex: Int): Byte = obj(columnIndex) as Byte

    override fun getByte(columnLabel: String?): Byte = obj(columnLabel) as Byte

    override fun getString(columnIndex: Int): String? = obj(columnIndex) as String?

    override fun getString(columnLabel: String?): String? = obj(columnLabel) as String?

    override fun updateSQLXML(
        columnIndex: Int,
        xmlObject: SQLXML?,
    ) {
        notImplemented()
    }

    override fun updateSQLXML(
        columnLabel: String?,
        xmlObject: SQLXML?,
    ) {
        notImplemented()
    }

    override fun updateDate(
        columnIndex: Int,
        x: Date?,
    ) {
        notImplemented()
    }

    override fun updateDate(
        columnLabel: String?,
        x: Date?,
    ) {
        notImplemented()
    }

    override fun getObject(columnIndex: Int): Any? = obj(columnIndex)

    override fun getObject(columnLabel: String?): Any? = obj(columnLabel)

    override fun getObject(
        columnIndex: Int,
        map: MutableMap<String, Class<*>>?,
    ): Any {
        // TODO Maybe?
        notImplemented()
    }

    override fun getObject(
        columnLabel: String?,
        map: MutableMap<String, Class<*>>?,
    ): Any {
        // TODO Maybe?
        notImplemented()
    }

    override fun <T : Any?> getObject(
        columnIndex: Int,
        type: Class<T>?,
    ): T = obj(columnIndex) as T

    override fun <T : Any?> getObject(
        columnLabel: String?,
        type: Class<T>?,
    ): T = obj(columnLabel) as T

    override fun previous(): Boolean {
        internalMove(cursor - 1)
        return cursorValid()
    }

    override fun updateDouble(
        columnIndex: Int,
        x: Double,
    ) {
        notImplemented()
    }

    override fun updateDouble(
        columnLabel: String?,
        x: Double,
    ) {
        notImplemented()
    }

    private fun castToLong(obj: Any?): Long {
        if (obj == null) {
            return 0
        } else if (obj is Long) {
            return obj
        } else if (obj is Number) {
            return obj.toLong()
        } else {
            throw IllegalStateException("Object is not a long!")
        }
    }

    override fun getLong(columnIndex: Int): Long = castToLong(obj(columnIndex))

    override fun getLong(columnLabel: String?): Long = castToLong(obj(columnLabel))

    override fun getClob(columnIndex: Int): Clob {
        // TODO Maybe?
        notImplemented()
    }

    override fun getClob(columnLabel: String?): Clob {
        // TODO Maybe?
        notImplemented()
    }

    override fun updateBlob(
        columnIndex: Int,
        x: Blob?,
    ) {
        notImplemented()
    }

    override fun updateBlob(
        columnLabel: String?,
        x: Blob?,
    ) {
        notImplemented()
    }

    override fun updateBlob(
        columnIndex: Int,
        inputStream: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateBlob(
        columnLabel: String?,
        inputStream: InputStream?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateBlob(
        columnIndex: Int,
        inputStream: InputStream?,
    ) {
        notImplemented()
    }

    override fun updateBlob(
        columnLabel: String?,
        inputStream: InputStream?,
    ) {
        notImplemented()
    }

    override fun updateByte(
        columnIndex: Int,
        x: Byte,
    ) {
        notImplemented()
    }

    override fun updateByte(
        columnLabel: String?,
        x: Byte,
    ) {
        notImplemented()
    }

    override fun updateRow() {
        notImplemented()
    }

    override fun deleteRow() {
        notImplemented()
    }

    override fun getNString(columnIndex: Int): String = obj(columnIndex) as String

    override fun getNString(columnLabel: String?): String = obj(columnLabel) as String

    override fun getArray(columnIndex: Int): Array {
        // TODO Maybe?
        notImplemented()
    }

    override fun getArray(columnLabel: String?): Array {
        // TODO Maybe?
        notImplemented()
    }

    override fun cancelRowUpdates() {
        notImplemented()
    }

    override fun updateString(
        columnIndex: Int,
        x: String?,
    ) {
        notImplemented()
    }

    override fun updateString(
        columnLabel: String?,
        x: String?,
    ) {
        notImplemented()
    }

    override fun setFetchDirection(direction: Int) {
        notImplemented()
    }

    override fun getCharacterStream(columnIndex: Int): Reader = getNCharacterStream(columnIndex)

    override fun getCharacterStream(columnLabel: String?): Reader = getNCharacterStream(columnLabel)

    override fun isBeforeFirst(): Boolean = cursor - 1 < resultSetLength

    override fun updateBoolean(
        columnIndex: Int,
        x: Boolean,
    ) {
        notImplemented()
    }

    override fun updateBoolean(
        columnLabel: String?,
        x: Boolean,
    ) {
        notImplemented()
    }

    override fun refreshRow() {
        notImplemented()
    }

    override fun rowUpdated() = false

    override fun updateBigDecimal(
        columnIndex: Int,
        x: BigDecimal?,
    ) {
        notImplemented()
    }

    override fun updateBigDecimal(
        columnLabel: String?,
        x: BigDecimal?,
    ) {
        notImplemented()
    }

    override fun getShort(columnIndex: Int): Short = obj(columnIndex) as Short

    override fun getShort(columnLabel: String?): Short = obj(columnLabel) as Short

    override fun getAsciiStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)

    override fun getAsciiStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)

    override fun updateTime(
        columnIndex: Int,
        x: Time?,
    ) {
        notImplemented()
    }

    override fun updateTime(
        columnLabel: String?,
        x: Time?,
    ) {
        notImplemented()
    }

    override fun getTimestamp(columnIndex: Int): Timestamp {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTimestamp(columnLabel: String?): Timestamp {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTimestamp(
        columnIndex: Int,
        cal: Calendar?,
    ): Timestamp {
        // TODO Maybe?
        notImplemented()
    }

    override fun getTimestamp(
        columnLabel: String?,
        cal: Calendar?,
    ): Timestamp {
        // TODO Maybe?
        notImplemented()
    }

    override fun getRef(columnIndex: Int): Ref {
        // TODO Maybe?
        notImplemented()
    }

    override fun getRef(columnLabel: String?): Ref {
        // TODO Maybe?
        notImplemented()
    }

    override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY

    override fun updateRowId(
        columnIndex: Int,
        x: RowId?,
    ) {
        notImplemented()
    }

    override fun updateRowId(
        columnLabel: String?,
        x: RowId?,
    ) {
        notImplemented()
    }

    override fun getNCharacterStream(columnIndex: Int): Reader = getBinaryStream(columnIndex).reader()

    override fun getNCharacterStream(columnLabel: String?): Reader = getBinaryStream(columnLabel).reader()

    override fun updateArray(
        columnIndex: Int,
        x: Array?,
    ) {
        notImplemented()
    }

    override fun updateArray(
        columnLabel: String?,
        x: Array?,
    ) {
        notImplemented()
    }

    override fun getBytes(columnIndex: Int): ByteArray = obj(columnIndex) as ByteArray

    override fun getBytes(columnLabel: String?): ByteArray = obj(columnLabel) as ByteArray

    override fun getDouble(columnIndex: Int): Double = obj(columnIndex) as Double

    override fun getDouble(columnLabel: String?): Double = obj(columnLabel) as Double

    override fun getUnicodeStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)

    override fun getUnicodeStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)

    override fun rowInserted() = false

    private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface)

    override fun isWrapperFor(iface: Class<*>?): Boolean = thisIsWrapperFor(iface) || parent.isWrapperFor(iface)

    override fun getInt(columnIndex: Int): Int = obj(columnIndex) as Int

    override fun getInt(columnLabel: String?): Int = obj(columnLabel) as Int

    override fun updateNull(columnIndex: Int) {
        notImplemented()
    }

    override fun updateNull(columnLabel: String?) {
        notImplemented()
    }

    override fun getRowId(columnIndex: Int): RowId {
        // TODO Maybe?
        notImplemented()
    }

    override fun getRowId(columnLabel: String?): RowId {
        // TODO Maybe?
        notImplemented()
    }

    override fun getMetaData(): ResultSetMetaData =
        object : ResultSetMetaData by parentMetadata {
            override fun isReadOnly(column: Int) = true

            override fun isWritable(column: Int) = false

            override fun isDefinitelyWritable(column: Int) = false

            override fun getColumnCount() = this@ScrollableResultSet.columnCount

            override fun getColumnLabel(column: Int): String = columnLabels[column - 1]
        }

    override fun getBinaryStream(columnIndex: Int): InputStream = (obj(columnIndex) as ByteArray).inputStream()

    override fun getBinaryStream(columnLabel: String?): InputStream = (obj(columnLabel) as ByteArray).inputStream()

    override fun updateCharacterStream(
        columnIndex: Int,
        x: Reader?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateCharacterStream(
        columnLabel: String?,
        reader: Reader?,
        length: Int,
    ) {
        notImplemented()
    }

    override fun updateCharacterStream(
        columnIndex: Int,
        x: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateCharacterStream(
        columnLabel: String?,
        reader: Reader?,
        length: Long,
    ) {
        notImplemented()
    }

    override fun updateCharacterStream(
        columnIndex: Int,
        x: Reader?,
    ) {
        notImplemented()
    }

    override fun updateCharacterStream(
        columnLabel: String?,
        reader: Reader?,
    ) {
        notImplemented()
    }

    class ResultSetEntry {
        val data = mutableListOf<Any?>()
    }
}
