package org.openrs2.cache

import io.netty.buffer.Unpooled
import org.openrs2.buffer.crc32
import org.openrs2.buffer.use
import org.sqlite.SQLiteDataSource
import java.nio.file.Files
import java.nio.file.Path
import java.sql.Connection

public object OpenNxtStore {
    public fun unpack(input: Path, output: Store) {
        output.create(Store.ARCHIVESET)

        for (archive in 0..Store.MAX_ARCHIVE) {
            val path = input.resolve("js5-$archive.jcache")
            if (!Files.exists(path)) {
                continue
            }

            val dataSource = SQLiteDataSource()
            dataSource.url = "jdbc:sqlite:$path"

            dataSource.connection.use { connection ->
                unpackArchive(connection, archive, output)
            }
        }
    }

    private fun unpackArchive(connection: Connection, archive: Int, output: Store) {
        connection.prepareStatement(
            """
            SELECT data, crc
            FROM cache_index
            WHERE key = 1
            """.trimIndent()
        ).use { stmt ->
            stmt.executeQuery().use { rows ->
                if (rows.next()) {
                    val checksum = rows.getInt(2)

                    Unpooled.wrappedBuffer(rows.getBytes(1)).use { buf ->
                        val actualChecksum = buf.crc32()
                        if (actualChecksum != checksum) {
                            throw StoreCorruptException(
                                "Js5Index corrupt (expected checksum $checksum, actual checksum $actualChecksum)"
                            )
                        }

                        output.write(Store.ARCHIVESET, archive, buf)
                    }
                }
            }
        }

        connection.prepareStatement(
            """
            SELECT key, data, crc, version
            FROM cache
            """.trimIndent()
        ).use { stmt ->
            stmt.executeQuery().use { rows ->
                while (rows.next()) {
                    val group = rows.getInt(1)
                    val checksum = rows.getInt(3)
                    val version = rows.getInt(4) and 0xFFFF

                    Unpooled.wrappedBuffer(rows.getBytes(2)).use { buf ->
                        val actualVersion = VersionTrailer.peek(buf)
                        if (actualVersion != version) {
                            throw StoreCorruptException(
                                "Group corrupt (expected version $version, actual version $actualVersion)"
                            )
                        }

                        val actualChecksum = buf.slice(buf.readerIndex(), buf.writerIndex() - 2).crc32()
                        if (actualChecksum != checksum) {
                            throw StoreCorruptException(
                                "Group corrupt (expected checksum $checksum, actual checksum $actualChecksum)"
                            )
                        }

                        output.write(archive, group, buf)
                    }
                }
            }
        }
    }
}
