package org.openrs2.archive.key

import org.openrs2.crypto.XteaKey
import org.openrs2.db.Database
import java.io.BufferedOutputStream
import java.io.DataOutputStream
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
public class KeyExporter @Inject constructor(
    private val database: Database
) {
    public data class Stats(
        val allKeys: Long,
        val validKeys: Long,
        val encryptedGroups: Long,
        val validGroups: Long,
        val emptyGroups: Long
    ) {
        val validKeysFraction: Double = if (allKeys == 0L) {
            1.0
        } else {
            validKeys.toDouble() / allKeys
        }

        val validGroupsFraction: Double = if (encryptedGroups == 0L) {
            1.0
        } else {
            validGroups.toDouble() / encryptedGroups
        }

        val emptyGroupsFraction: Double = if (encryptedGroups == 0L) {
            1.0
        } else {
            emptyGroups.toDouble() / encryptedGroups
        }
    }

    public suspend fun count(): Stats {
        return database.execute { connection ->
            val encryptedGroups: Long
            val validGroups: Long
            val emptyGroups: Long

            connection.prepareStatement(
                """
                SELECT
                    COUNT(*),
                    COUNT(*) FILTER (WHERE c.key_id IS NOT NULL),
                    COUNT(*) FILTER (WHERE c.key_id IS NULL AND c.empty_loc)
                FROM containers c
                WHERE c.encrypted
            """.trimIndent()
            ).use { stmt ->
                stmt.executeQuery().use { rows ->
                    check(rows.next())

                    encryptedGroups = rows.getLong(1)
                    validGroups = rows.getLong(2)
                    emptyGroups = rows.getLong(3)
                }
            }

            connection.prepareStatement(
                """
                SELECT
                    COUNT(DISTINCT k.id),
                    COUNT(DISTINCT k.id) FILTER (WHERE c.key_id IS NOT NULL)
                FROM keys k
                LEFT JOIN containers c ON c.key_id = k.id
            """.trimIndent()
            ).use { stmt ->
                stmt.executeQuery().use { rows ->
                    check(rows.next())

                    val allKeys = rows.getLong(1)
                    val validKeys = rows.getLong(2)
                    Stats(allKeys, validKeys, encryptedGroups, validGroups, emptyGroups)
                }
            }
        }
    }

    public suspend fun exportAll(): List<XteaKey> {
        return export(validOnly = false)
    }

    public suspend fun exportValid(): List<XteaKey> {
        return export(validOnly = true)
    }

    public suspend fun analyse(): String {
        val keys = exportValid()

        val process = ProcessBuilder("ent")
            .redirectError(ProcessBuilder.Redirect.INHERIT)
            .start()

        DataOutputStream(BufferedOutputStream(process.outputStream)).use { out ->
            for (key in keys) {
                out.writeInt(key.k0)
                out.writeInt(key.k1)
                out.writeInt(key.k2)
                out.writeInt(key.k3)
            }
        }

        val analysis = process.inputStream.readAllBytes().toString(Charsets.UTF_8)

        val status = process.waitFor()
        if (status != 0) {
            throw Exception("ent failed: $status")
        }

        return analysis
    }

    private suspend fun export(validOnly: Boolean): List<XteaKey> {
        return database.execute { connection ->
            val query = if (validOnly) {
                EXPORT_VALID_QUERY
            } else {
                EXPORT_ALL_QUERY
            }

            connection.prepareStatement(query).use { stmt ->
                stmt.executeQuery().use { rows ->
                    val keys = mutableListOf<XteaKey>()

                    while (rows.next()) {
                        val k0 = rows.getInt(1)
                        val k1 = rows.getInt(2)
                        val k2 = rows.getInt(3)
                        val k3 = rows.getInt(4)
                        keys += XteaKey(k0, k1, k2, k3)
                    }

                    keys
                }
            }
        }
    }

    private companion object {
        private val EXPORT_ALL_QUERY = """
            SELECT (k.key).k0, (k.key).k1, (k.key).k2, (k.key).k3
            FROM keys k
            ORDER BY k.id ASC
        """.trimIndent()

        private val EXPORT_VALID_QUERY = """
            SELECT (k.key).k0, (k.key).k1, (k.key).k2, (k.key).k3
            FROM keys k
            JOIN containers c ON c.key_id = k.id
            ORDER BY k.id ASC
        """.trimIndent()
    }
}
