package com.nisemoe.nise

import com.nisemoe.nise.konata.Replay
import com.nisemoe.nise.konata.compareReplayPair
import com.nisemoe.nise.konata.compareReplaySet
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.roundToInt
import kotlin.system.measureTimeMillis
import kotlin.test.assertEquals

class ReplayTest {

    data class ReplayDto(
        val replay1: String,
        val replay2: String,
        val similarity: Double
    )

    @Test
    fun accuracyTestv2() {

        // Replay 1 id = 2451831831
        // Replay 2 id = 2460830344

        val replay1String =
            "[encoded data, 25856 characters]"
        val replay2String =
            "[encoded data, 24072 characters]"

        val replay1 = Replay(replay1String, 2451831831, mods = 1136)
        val replay2 = Replay(replay2String, 2460830344, mods = 1104)

        val result = compareReplayPair(replay1, replay2)

        // konata returned 0.9841655093578295
        assertEquals(0.9852628898202029, result.correlation, 0.01)

        // konata returned 30.038441329862874
        assertEquals(27.3528622905985, result.similarity, 1.0)
    }

    @Test
    fun accuracyTest() {
        val filePath = Paths.get("src", "test", "resources", "replays.txt")
        fun createComparison(lines: List<String>): ReplayDto {
            return ReplayDto(lines[0], lines[1], lines[2].toDouble())
        }

        val lines = Files.readAllLines(filePath)

        val comparisons = lines.windowed(size = 3, step = 3)
            .map(::createComparison)

        comparisons.forEach {
            val result = compareReplayPair(it.replay1, it.replay2)
            assertEquals(it.similarity, result.similarity, 1.0, "Replay similarity is not accurate")
        }
    }

    @Test
    fun integrityTest() {
        val filePath = Paths.get("src", "test", "resources", "replays_1.txt")

        val replayDataFromFile: List<String> = Files.readAllLines(filePath)

        val replays = replayDataFromFile.map {
            Replay(it, it.hashCode().toLong())
        }.toTypedArray()

        // NoMod
        val replay1 = replays[0]
        val replay2 = replays[1]

        val firstVector = replay1.vector.copy()
        val secondVector = replay2.vector.copy()

        compareReplayPair(replay1, replay2)

        assertEquals(firstVector, replay1.vector, "Replay 1 vector was modified")
        assertEquals(secondVector, replay2.vector, "Replay 2 vector was modified")

        // Hard Rock
        val replay3 = replays[2]
        replay3.mods = 24

        val replay4 = replays[3]
        replay4.mods = 0

        val firstVectorHR = replay3.vector.copy()
        val secondVectorHR = replay4.vector.copy()

        compareReplayPair(replay3, replay4)

        assertEquals(firstVectorHR, replay3.vector, "Replay 3 vector was modified")
        assertEquals(secondVectorHR, replay4.vector, "Replay 4 vector was modified")
    }

    @Test
    @Disabled
    fun performanceTest() {
        val filePath = Paths.get("src", "test", "resources", "replays_1.txt")

        val replayDataFromFile: List<String> = Files.readAllLines(filePath)

        val totalPairs = AtomicInteger(0)
        val executionTimes = mutableListOf<Long>()

        for (i in 1..10) {
            val timeTaken = measureTimeMillis {
                val replays = replayDataFromFile.map {
                    Replay(it, it.hashCode().toLong())
                }.toTypedArray()
                val result = compareReplaySet(replays)
                totalPairs.addAndGet(result.size)
            }
            executionTimes.add(timeTaken)
        }

        val minTime = executionTimes.minOrNull() ?: 0L
        val maxTime = executionTimes.maxOrNull() ?: 0L
        val avgTime = executionTimes.average()
        val totalTime = executionTimes.sum()
        val pairsPerSecond = totalPairs.get() / (totalTime / 1000.0)

        println("Min Time: $minTime ms")
        println("Max Time: $maxTime ms")
        println("Average Time: $avgTime ms")
        println("Total Time: ${totalTime / 1000.0} seconds")
        println("Total Pairs per Second: ${pairsPerSecond.roundToInt()}/s")
    }

}