package net.taehui.twilight.awilight

import net.taehui.twilight.BaseCompiler
import net.taehui.twilight.Component
import net.taehui.twilight.Computing
import net.taehui.twilight.JSON
import net.taehui.twilight.Utility.getDistance
import net.taehui.twilight.note.BaseNote
import net.taehui.twilight.note.InputNote
import net.taehui.twilight.note.JudgedNoteData
import net.taehui.twilight.note.JudgedNoteData.ID
import org.apache.commons.lang3.time.StopWatch
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.math.abs

class DefaultCompute(noteID: String, val avatar: AwilightAvatar, val handlerID: String, val site: AwilightSite) : Computing(noteID) {
    val ioAvatarIDs = mutableListOf<String>()
    val pendingIOAvatarIDs = mutableListOf<String>()
    private val sentIOAvatarIDs = mutableListOf<String>()
    val modeComponentValue = ModeComponent()
    val comment: CommentOuterClass.Comment.Builder =
        CommentOuterClass.Comment.newBuilder().setDate("2.0.0")
    private val handlingNotes = mutableListOf<BaseNote>()
    val notes = mutableListOf<BaseNote>()
    val waitAudioNoteMap = TreeSet<Double>()
    val waitMediaNoteMap = TreeSet<Double>()
    val waitBPMMap = TreeMap<Double, Double>()
    val netDrawings = mutableListOf<EventOuterClass.Event.NetDrawing>()
    private val r = Random()
    private lateinit var noteFileData: ByteArray
    var setStop = false
    var targetStand = 0
    var isF = false
    private var targetHitPoints = 1.0
    private var isFailed = false
    private var lastJudged = Component.Judged.NOT
    private var targetBand = 0
    private var highestBand = 0
    private var targetPoint = 1.0
    private var savedPoint = 0.0
    private var totalPoint = 0.0
    private var savedStand = 0.0
    private var earlyValue = 0
    private var lateValue = 0
    val twilightCompiledIOQueue = ConcurrentLinkedQueue<JSON.TwilightCompiledIO>()

    fun stop() {
        setStop = true
        avatar.send(EventOuterClass.Event.EventID.COMPILED, object {
            val siteID = site.siteID
            val handlerID = this@DefaultCompute.handlerID

            @JvmField
            val isCompiled = false
        })
    }

    private fun setNoteJudged(inputNote: BaseNote, judged: Component.Judged) {
        inputNote.judged = judged
        if (sentIOAvatarIDs.isNotEmpty()) {
            avatar.send(
                EventOuterClass.Event.newBuilder().apply {
                    eventID = EventOuterClass.Event.EventID.IO_JUDGE
                    qwilightIOJudge = EventOuterClass.Event.QwilightIOJudge.newBuilder().apply {
                        handlerID = this@DefaultCompute.handlerID
                        addAllAvatarIDs(sentIOAvatarIDs)
                        noteID = inputNote.noteID
                        this.judged = judged.value
                    }.build()
                }
            )
        }
    }

    private fun setNoteFailed(note: BaseNote, setValidJudgedNotes: Boolean) {
        note.isFailed = true
        if (sentIOAvatarIDs.isNotEmpty()) {
            avatar.send(
                EventOuterClass.Event.newBuilder().apply {
                    eventID = EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY
                    qwilightIONoteVisibility = EventOuterClass.Event.QwilightIONoteVisibility.newBuilder().apply {
                        handlerID = this@DefaultCompute.handlerID
                        addAllAvatarIDs(sentIOAvatarIDs)
                        noteID = note.noteID
                        this.setValidJudgedNotes = setValidJudgedNotes
                        setNoteFailed = true
                    }.build()
                }
            )
        }
    }

    private fun wipeNotes(note: BaseNote, setValidJudgedNotes: Boolean) {
        handlingNotes.remove(note)
        if (note.hasStand() && sentIOAvatarIDs.isNotEmpty()) {
            avatar.send(
                EventOuterClass.Event.newBuilder().apply {
                    eventID = EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY
                    qwilightIONoteVisibility = EventOuterClass.Event.QwilightIONoteVisibility.newBuilder().apply {
                        handlerID = this@DefaultCompute.handlerID
                        addAllAvatarIDs(sentIOAvatarIDs)
                        this.noteID = note.noteID
                        this.setValidJudgedNotes = setValidJudgedNotes
                        setNoteFailed = false
                    }.build()
                }
            )
        }
    }

    fun handleCompile(noteFilePath: Path) {
        noteFileData = Files.readAllBytes(noteFilePath)
        BaseCompiler.handleCompile(this, noteFileData)
        avatar.send(EventOuterClass.Event.EventID.COMPILED, object {
            val siteID = site.siteID
            val handlerID = this@DefaultCompute.handlerID

            @JvmField
            val isCompiled = true
        })
    }

    fun handleNotes(w: Double) {
        modeComponentValue.bpm = bpm
        comment.levyingMultiplier = modeComponentValue.getPaidMultiplier()
        comment.levyingAudioMultiplier = modeComponentValue.audioMultiplier
        val inputCount = Component.INPUT_COUNTS[inputMode.value]
        val drawingComponent = EventOuterClass.Event.DrawingComponent.newBuilder().apply {
            this.p2BuiltLength = (InputNote.NOTE_LENGTH * inputCount).toFloat()
            this.judgmentMainPosition = Component.STANDARD_HEIGHT.toFloat()
        }.build()
        var handlingID = 0
        var loopingCounter = -Component.LEVYING_WAIT
        val valueComponent = Component(levyingBPM, 60)
        var logicalY = 0.0
        val endNoteID = notes.size
        var handledNotYet = true
        val inputMillisMap = DoubleArray(53)
        val waitBPMQueue = LinkedList<Map.Entry<Double, Double>>(waitBPMMap.entries)
        val rawInputQueue = LinkedList<Int>()
        val inputMap = BooleanArray(53)
        val inputCoolMillisMap = DoubleArray(53)
        val loopingHandler = StopWatch.createStarted()
        while (!setStop) {
            fun onNoteJudged(judgedNote: BaseNote) {
                val judged = judgedNote.judged
                if (judged == Component.Judged.LOWEST) {
                    targetBand = 0
                } else {
                    highestBand = (++targetBand).coerceAtLeast(highestBand)
                }
                when (judged) {
                    Component.Judged.HIGHEST -> comment.highestJudgment += 1
                    Component.Judged.HIGHER -> comment.higherJudgment += 1
                    Component.Judged.HIGH -> comment.highJudgment += 1
                    Component.Judged.LOW -> comment.lowJudgment += 1
                    Component.Judged.LOWER -> comment.lowerJudgment += 1
                    Component.Judged.LOWEST -> {
                        comment.lowestJudgment += 1
                        isFailed = true
                    }

                    else -> Unit
                }
                lastJudged = judged
                targetPoint = Component.POINT_MAP[judged.value]
                    .let { savedPoint += it; savedPoint } / Component.POINT_MAP[Component.Judged.HIGHEST.value].let { totalPoint += it; totalPoint }
                targetStand =
                    (1000000 * ((Component.STAND_MAP[judged.value])
                        .let { savedStand += it; savedStand }) / (totalNotes * Component.STAND_MAP[Component.Judged.HIGHEST.value])).toInt()
                targetHitPoints += (if (judged != Component.Judged.LOWEST) hitPointsValue else 1.5 * targetHitPoints + 0.5) * Component.HIT_POINTS_MAP[judged.value]
                targetHitPoints = targetHitPoints.coerceIn(0.0, 1.0)
                comment.addPaints(
                    CommentOuterClass.PaintEvent.newBuilder().setWait(loopingCounter).setHitPoints(targetHitPoints)
                        .setStand(targetStand)
                        .setBand(targetBand)
                        .setPoint(targetPoint).build()
                )
            }

            fun setJudgmentMeter(
                loopingCounter: Double,
                input: Int,
                judgmentMeter: Double,
                judgmentAssist: Component.JudgmentAssist
            ) {
                comment.addJudgmentMeters(
                    CommentOuterClass.JudgmentMeterEvent.newBuilder().setJudgmentMeter(judgmentMeter)
                        .setWait(loopingCounter - judgmentMeter).setAssist(judgmentAssist.ordinal).build()
                )
                if (judgmentMeter > 0.0) {
                    ++lateValue
                } else if (judgmentMeter < 0.0) {
                    ++earlyValue
                }
                if (sentIOAvatarIDs.isNotEmpty()) {
                    avatar.send(
                        EventOuterClass.Event.newBuilder().apply {
                            eventID = EventOuterClass.Event.EventID.IO_JUDGMENT_METER
                            qwilightIOJudgmentMeter = EventOuterClass.Event.QwilightIOJudgmentMeter.newBuilder().apply {
                                addAllAvatarIDs(sentIOAvatarIDs)
                                handlerID = this@DefaultCompute.handlerID
                                this.judgmentMeter = judgmentMeter
                                this.input = input
                                this.assist = judgmentAssist.ordinal
                            }.build()
                        }
                    )
                }
            }

            fun handleJudged(judgedNote: BaseNote, judgedNoteData: JudgedNoteData?, loopingCounter: Double) {
                judgedNoteData?.let {
                    val input = judgedNote.input
                    val judged = it.judged
                    when (it.valueID) {
                        ID.NOTE_JUDGMENT -> {
                            setNoteJudged(judgedNote, judged)
                            wipeNotes(judgedNote, false)
                            setJudgmentMeter(loopingCounter, input, it.judgmentMeter, Component.JudgmentAssist.DEFAULT)
                            onNoteJudged(judgedNote)
                        }

                        ID.TRAP_NOTE_JUDGMENT -> {
                            setNoteJudged(judgedNote, judged)
                            wipeNotes(judgedNote, false)
                            onNoteJudged(judgedNote)
                        }

                        ID.QUIT_LONG_NOTE_JUDGMENT, ID.AUTO_LONG_NOTE_JUDGMENT -> {
                            setNoteJudged(judgedNote, judged)
                            wipeNotes(judgedNote, false)
                            setJudgmentMeter(
                                loopingCounter,
                                input,
                                it.judgmentMeter,
                                Component.JudgmentAssist.LONG_NOTE_UP
                            )
                            onNoteJudged(judgedNote)
                        }

                        ID.LEVY_LONG_NOTE_JUDGMENT -> {
                            if (judged == Component.Judged.LOWEST) {
                                setNoteFailed(judgedNote, true)
                            }
                            setNoteJudged(judgedNote, judged)
                            setJudgmentMeter(loopingCounter, input, it.judgmentMeter, Component.JudgmentAssist.DEFAULT)
                            onNoteJudged(judgedNote)
                        }

                        ID.FAILED_LONG_NOTE_JUDGMENT -> {
                            setNoteFailed(judgedNote, false)
                            setNoteJudged(judgedNote, Component.Judged.LOWEST)
                            onNoteJudged(judgedNote)
                        }

                        else -> Unit
                    }
                }
            }

            fun sendCallNetEvent(avatarNetStatus: EventOuterClass.Event.AvatarNetStatus) {
                avatar.send(EventOuterClass.Event.newBuilder().apply {
                    this.eventID = EventOuterClass.Event.EventID.CALL_NET
                    this.qwilightCallNet = EventOuterClass.Event.QwilightCallNet.newBuilder().apply {
                        this.siteID = site.siteID
                        this.handlerID = this@DefaultCompute.handlerID
                        this.hitPointsMode = modeComponentValue.hitPointsMode
                        this.avatarNetStatus = avatarNetStatus
                        this.stand = targetStand
                        this.highestBand = this@DefaultCompute.highestBand
                        this.point = targetPoint
                        this.hitPoints = targetHitPoints
                        this.isFailed = this@DefaultCompute.isFailed
                        this.lastJudged = this@DefaultCompute.lastJudged.value
                        if (avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default) {
                            this.title = this@DefaultCompute.title
                            this.artist = this@DefaultCompute.artist
                            this.genre = this@DefaultCompute.genre
                            this.level = this@DefaultCompute.level
                            this.levelText = this@DefaultCompute.levelText
                            this.wantLevelID = ""
                            val modeComponentValue = this@DefaultCompute.modeComponentValue
                            val comment = this@DefaultCompute.comment
                            this.autoMode = modeComponentValue.autoMode
                            this.noteSaltMode = modeComponentValue.noteSaltMode
                            this.audioMultiplier = comment.levyingAudioMultiplier
                            this.faintNoteMode = modeComponentValue.faintNoteMode
                            this.judgmentMode = modeComponentValue.judgmentMode
                            this.noteMobilityMode = modeComponentValue.noteMobilityMode
                            this.inputFavorMode = modeComponentValue.inputFavorMode
                            this.longNoteMode = modeComponentValue.longNoteMode
                            this.noteModifyMode = modeComponentValue.noteModifyMode
                            this.bpmMode = modeComponentValue.bpmMode
                            this.waveMode = modeComponentValue.waveMode
                            this.setNoteMode = modeComponentValue.setNoteMode
                            this.lowestJudgmentConditionMode = modeComponentValue.lowestJudgmentConditionMode
                            this.totalNotes = this@DefaultCompute.totalNotes
                            this.judgmentStage = this@DefaultCompute.judgmentStage
                            this.hitPointsValue = this@DefaultCompute.hitPointsValue
                            this.highestInputCount = this@DefaultCompute.highestInputCount
                            this.length = this@DefaultCompute.length
                            this.bpm = this@DefaultCompute.bpm
                            this.multiplier = comment.levyingMultiplier
                            this.inputMode = this@DefaultCompute.inputMode.value
                            addData(comment.build().toByteString())
                        } else {
                            this.drawingComponent = drawingComponent
                            this.addAllDrawings(this@DefaultCompute.netDrawings)
                        }
                    }.build()
                })
                isFailed = false
            }

            val lastLoopingCounter = loopingCounter
            loopingCounter += valueComponent.millisLoopUnit
            if (loopingCounter > length + Component.QUIT_WAIT && handledNotYet) {
                handledNotYet = false
                if (!isF) {
                    sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Clear)
                }
            }

            while (true) {
                val twilightCompiledIO = twilightCompiledIOQueue.poll()
                if (twilightCompiledIO != null) {
                    if (twilightCompiledIO.isCompiled) {
                        ioAvatarIDs.add(twilightCompiledIO.avatarID)
                        pendingIOAvatarIDs.add(twilightCompiledIO.avatarID)
                    } else {
                        ioAvatarIDs.remove(twilightCompiledIO.avatarID)
                        pendingIOAvatarIDs.remove(twilightCompiledIO.avatarID)
                        sentIOAvatarIDs.remove(twilightCompiledIO.avatarID)
                    }
                } else {
                    break
                }
            }

            // 인공지능
            for (i in inputCount downTo 1) {
                val powValue0 = 125.0 * r.nextGaussian(0.0, 1 - w)
                val powValue1 = abs(r.nextGaussian(0.0, w) * valueComponent.millisLoopUnit)

                inputCoolMillisMap[i] = 0.0.coerceAtLeast(inputCoolMillisMap[i] - valueComponent.millisLoopUnit)
                if (inputCoolMillisMap[i] == 0.0) {
                    if (inputMap[i]) {
                        rawInputQueue.offer(-i)
                        inputMap[i] = false
                    } else {
                        handlingNotes
                            .filter { it.hasStand() && it.input == i && it.judged == Component.Judged.NOT }
                            .minByOrNull { it.wait }
                            ?.let {
                                if (it.wait + inputMillisMap[i] <= loopingCounter) {
                                    rawInputQueue.offer(i)
                                    inputMap[i] = true

                                    inputCoolMillisMap[i] += it.longWait + powValue0
                                    inputMillisMap[i] = powValue0
                                }
                            }
                    }
                }

                val highestJudgmentMillis0 =
                    Component.getJudgmentMillis(
                        Component.Judged.HIGHEST,
                        judgmentStage,
                        0,
                        Component.JudgmentAssist.DEFAULT
                    ) - valueComponent.millisLoopUnit
                val highestJudgmentMillis1 =
                    Component.getJudgmentMillis(
                        Component.Judged.HIGHEST,
                        judgmentStage,
                        1,
                        Component.JudgmentAssist.DEFAULT
                    )
                if (inputMillisMap[i] < highestJudgmentMillis0) {
                    inputMillisMap[i] = inputMillisMap[i] + powValue1
                } else if (highestJudgmentMillis1 < inputMillisMap[i]) {
                    inputMillisMap[i] = inputMillisMap[i] - powValue1
                }
            }

            // 렌더링 스레드
            netDrawings.clear()
            for (i in inputMap.size - 1 downTo 1) {
                if (inputMap[i]) {
                    netDrawings.add(EventOuterClass.Event.NetDrawing.newBuilder().apply {
                        this.drawingVariety = EventOuterClass.Event.NetDrawing.Variety.Main
                        this.param = Component.NET_DRAWINGS[inputMode.value]!![i] - 128
                        this.position0 = (i - 1) * InputNote.NOTE_LENGTH
                        this.position1 = 0.0
                        this.length = InputNote.NOTE_LENGTH
                        this.height = Component.STANDARD_HEIGHT
                    }.build())
                }
            }
            handlingNotes.forEach {
                it.paint(this)
            }

            if (pendingIOAvatarIDs.isNotEmpty()) {
                avatar.send(
                    EventOuterClass.Event.EventID.LEVY_IO,
                    object {
                        val avatarIDs = pendingIOAvatarIDs
                        val handlerID = this@DefaultCompute.handlerID
                        val levyingWait = loopingCounter

                        @JvmField
                        val isF = this@DefaultCompute.isF
                        val multiplier = modeComponentValue.getPaidMultiplier()
                        val audioMultiplier = modeComponentValue.audioMultiplier
                        val ioMillis = System.currentTimeMillis()
                    },
                    comment.build().toByteString()
                )
                sentIOAvatarIDs.addAll(pendingIOAvatarIDs)
                pendingIOAvatarIDs.clear()
            }

            while (endNoteID > handlingID) {
                val note = notes[handlingID]
                if (note.isClose(loopingCounter, logicalY)) {
                    handlingNotes.add(note)
                    ++handlingID
                } else {
                    break
                }
            }

            val distance = getDistance(
                valueComponent,
                waitBPMQueue,
                lastLoopingCounter,
                loopingCounter
            )
            logicalY -= distance

            for (i in handlingNotes.size - 1 downTo 0) {
                fun handleAutoJudged(note: BaseNote): Boolean {
                    return when (note.autoJudge(loopingCounter)?.valueID) {
                        ID.NOT -> true
                        else -> false
                    }
                }

                val handlingNote = handlingNotes[i]
                handlingNote.doMove(distance)
                if (handleAutoJudged(handlingNote)) {
                    wipeNotes(handlingNote, false)
                } else {
                    if (handlingNote.hasStand()) {
                        if (handlingNote.isFailedAsTooLate(
                                loopingCounter,
                                judgmentStage
                            ) && handlingNote.judged == Component.Judged.NOT
                        ) {
                            setNoteJudged(handlingNote, Component.Judged.LOWEST)
                            if (handlingNote.longWait > 0.0) {
                                setNoteFailed(handlingNote, true)
                            } else {
                                wipeNotes(handlingNote, false)
                            }
                            onNoteJudged(handlingNote)
                        }
                        if (handlingNote.judged != Component.Judged.NOT && handlingNote.isTooLong(
                                loopingCounter,
                                judgmentStage
                            )
                        ) {
                            if (!handlingNote.isFailed) {
                                if (isAutoLongNote) {
                                    setNoteJudged(handlingNote, handlingNote.judged)
                                    onNoteJudged(handlingNote)
                                } else {
                                    setNoteJudged(handlingNote, Component.Judged.LOWER)
                                    onNoteJudged(handlingNote)
                                }
                            }
                            wipeNotes(handlingNote, isAutoLongNote)
                        }
                    }
                }
            }

            while (true) {
                val rawInput = rawInputQueue.poll()
                if (rawInput != null) {
                    val isInput = rawInput > 0
                    val absInput = abs(rawInput)
                    comment.addInputs(
                        CommentOuterClass.InputEvent.newBuilder().setInput(rawInput).setWait(loopingCounter).build()
                    )
                    if (sentIOAvatarIDs.isNotEmpty()) {
                        avatar.send(
                            EventOuterClass.Event.newBuilder().apply {
                                eventID = EventOuterClass.Event.EventID.IO_INPUT
                                qwilightIOInput = EventOuterClass.Event.QwilightIOInput.newBuilder().apply {
                                    handlerID = this@DefaultCompute.handlerID
                                    addAllAvatarIDs(sentIOAvatarIDs)
                                    this.input = rawInput
                                    power = UByte.MAX_VALUE.toInt()
                                }.build()
                            }
                        )
                    }

                    var wasHandled = false
                    handlingNotes
                        .filter { it.input == absInput && !it.isFailed && it.hasStand() && (!isInput || it.judged == Component.Judged.NOT) }
                        .minByOrNull { it.wait }
                        ?.let {
                            val judgedNoteData = it.judge(rawInput, loopingCounter, judgmentStage, isAutoLongNote)
                            if (judgedNoteData != null) {
                                handleJudged(it, judgedNoteData, loopingCounter)
                            }
                            wasHandled = true
                        }

                    if (!wasHandled) {
                        handlingNotes
                            .minByOrNull { it.wait }
                            ?.let {
                                if (it.input == absInput && !it.isFailed && !it.hasStand()) {
                                    val judgedNoteData =
                                        it.judge(rawInput, loopingCounter, judgmentStage, isAutoLongNote)
                                    if (judgedNoteData != null) {
                                        handleJudged(it, judgedNoteData, loopingCounter)
                                        wasHandled = true
                                    }
                                }
                            }
                    }
                } else {
                    break
                }
            }

            if (targetHitPoints == 0.0 && !isF) {
                isF = true
                sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Failed)
                isFailed = false
            }

            if (!isF) {
                sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Default)
            }

            val toLoop = loopingCounter - (loopingHandler.time - Component.LEVYING_WAIT)
            if (toLoop > 0) {
                Thread.sleep(toLoop.toLong())
            }
        }
        if (ioAvatarIDs.isNotEmpty()) {
            avatar.send(
                EventOuterClass.Event.EventID.IO_QUIT,
                object {
                    val handlerID = this@DefaultCompute.handlerID
                    val avatarIDs = ioAvatarIDs

                    @JvmField
                    val isBanned = false
                }
            )
        }
    }
}