package de.arxsilex.maptools.converter.core.elevation

import com.sun.management.OperatingSystemMXBean
import java.lang.management.ManagementFactory

data class ElevationExecutionOptions(
    val workerCount: Int,
    val bufferBytesPerWorker: Int,
) {
    init {
        require(workerCount >= 1) { "Mindestens ein Höhen-Worker ist erforderlich." }
        require(bufferBytesPerWorker >= MIN_BUFFER_BYTES) {
            "Der Höhen-Worker-Puffer muss mindestens $MIN_BUFFER_BYTES Bytes groß sein."
        }
    }

    companion object {
        internal const val MIN_BUFFER_BYTES = 256 * 1024
    }
}

/** Selects bounded CPU and buffer usage from the resources that are available to the worker. */
object ElevationExecutionPolicy {
    private const val MIB = 1024L * 1024L
    private const val GIB = 1024L * MIB
    private const val MAX_WORKERS = 16
    private const val MAX_BUFFER_BYTES = 8 * 1024 * 1024
    private const val MEMORY_PER_WORKER = 32L * MIB
    private const val MIN_SYSTEM_RESERVE = 768L * MIB

    fun detect(): ElevationExecutionOptions {
        val runtime = Runtime.getRuntime()
        val operatingSystem = ManagementFactory.getOperatingSystemMXBean() as? OperatingSystemMXBean
        val usedHeap = runtime.totalMemory() - runtime.freeMemory()
        return from(
            availableProcessors = runtime.availableProcessors(),
            totalMemoryBytes = operatingSystem?.totalMemorySize ?: runtime.maxMemory(),
            freeMemoryBytes = operatingSystem?.freeMemorySize ?: (runtime.maxMemory() - usedHeap),
            heapHeadroomBytes = (runtime.maxMemory() - usedHeap).coerceAtLeast(0L),
        )
    }

    internal fun from(
        availableProcessors: Int,
        totalMemoryBytes: Long,
        freeMemoryBytes: Long,
        heapHeadroomBytes: Long,
    ): ElevationExecutionOptions {
        val processors = availableProcessors.coerceAtLeast(1)
        val cpuWorkers = (processors - 1).coerceIn(1, MAX_WORKERS)
        val systemReserve = maxOf(MIN_SYSTEM_RESERVE, totalMemoryBytes.coerceAtLeast(0L) / 4L)
        val physicalBudget = (freeMemoryBytes.coerceAtLeast(0L) - systemReserve).coerceAtLeast(MEMORY_PER_WORKER)
        val heapBudget = heapHeadroomBytes.coerceAtLeast(MEMORY_PER_WORKER)
        val memoryWorkers = (minOf(physicalBudget, heapBudget) / MEMORY_PER_WORKER)
            .coerceIn(1L, MAX_WORKERS.toLong())
            .toInt()
        val workers = minOf(cpuWorkers, memoryWorkers)
        val perWorkerBudget = (minOf(physicalBudget, heapBudget) / workers)
        val bufferBytes = when {
            perWorkerBudget >= 64L * MIB -> MAX_BUFFER_BYTES
            perWorkerBudget >= 32L * MIB -> 4 * 1024 * 1024
            perWorkerBudget >= 16L * MIB -> 2 * 1024 * 1024
            else -> 1024 * 1024
        }
        return ElevationExecutionOptions(workers, bufferBytes)
    }
}
