package net.taehui.twilight.awilight

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import net.taehui.twilight.*
import net.taehui.twilight.system.*
import org.apache.hc.client5.http.classic.methods.HttpGet
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.python.core.Py
import org.python.core.PyFunction
import org.python.core.PyObject
import org.python.util.PythonInterpreter
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.pathString

class AwilightAvatar : SimpleChannelInboundHandler<EventOuterClass.Event>(), Logger {
    private lateinit var handler: ChannelHandlerContext
    private val enteredSites = mutableMapOf<String, AwilightSite>()
    private val defaultComputerCSX = Any()
    private val w = Math.random().coerceIn(0.3, 0.7)
    private val awilightCaller = PythonInterpreter()
    val qwilightName = QwilightNamesSystem.qwilightName
    var defaultComputer: DefaultCompute? = null
    var avatarID = ""

    init {
        loadAwilight()
    }

    val isBeginner: Boolean
        get() {
            return w < 0.4
        }

    fun loadAwilight() {
        awilightCaller.cleanup()
        awilightCaller["awilight"] = this
        awilightCaller.execfile(TwilightComponent.AWILIGHT_FILE_PATH.pathString)
    }

    fun dispose() {
        WitSystem.wipeWit(this)
        synchronized(defaultComputerCSX) {
            defaultComputer?.stop()
        }
    }

    private fun on(function: String, params: Array<PyObject>) {
        awilightCaller.get(function, PyFunction::class.java)?.__call__(params)
    }

    fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString) {
        handler.writeAndFlush(EventOuterClass.Event.newBuilder().apply {
            this.millis = System.currentTimeMillis()
            this.avatarID = this@AwilightAvatar.avatarID
            this.eventID = eventID
            if (text != null) {
                this.text = if (text is String) text else ObjectMapper().writeValueAsString(text)
            }
            this.addAllData(data.toList())
        }.build())
    }

    fun send(event: EventOuterClass.Event.Builder) {
        event.millis = System.currentTimeMillis()
        event.avatarID = avatarID
        handler.writeAndFlush(event.build())
    }

    fun yell(siteID: String, siteYell: String) {
        send(EventOuterClass.Event.EventID.SITE_YELL, object {
            val siteID = siteID
            val siteYell = siteYell
        })
    }

    fun setSiteHand(siteID: String, avatarID: String) {
        synchronized(enteredSites) {
            enteredSites[siteID]?.let {
                if (it.isSiteHand) {
                    send(EventOuterClass.Event.EventID.SET_SITE_HAND, object {
                        val siteID = siteID
                        val avatarID = avatarID
                    })
                }
            }
        }
    }


    fun stopSiteNet(siteID: String) {
        synchronized(enteredSites) {
            enteredSites[siteID]?.let {
                if (it.isSiteHand) {
                    send(EventOuterClass.Event.EventID.QUIT_NET, siteID)
                }
            }
        }
    }


    fun handleSiteNet(siteID: String) {
        synchronized(enteredSites) {
            enteredSites[siteID]?.let {
                if (it.isSiteHand && it.avatarConfigure == AwilightSite.AVATAR_CONFIGURE_DEFAULT) {
                    send(EventOuterClass.Event.EventID.LEVY_NET, siteID)
                }
            }
        }
    }

    fun quitSite(siteID: String) {
        send(EventOuterClass.Event.EventID.QUIT_SITE, siteID)
    }

    fun setNoteFile(siteID: String) {
        synchronized(enteredSites) {
            enteredSites[siteID]?.let {
                if (it.isSiteHand) {
                    NoteFilesSystem.noteFile?.let { (noteID, noteFilePath) ->
                        val targetComputing = BaseCompiler.handleCompile(Files.readAllBytes(noteFilePath), -1).random()
                        send(
                            EventOuterClass.Event.EventID.SET_NOTE_FILE,
                            object {
                                val siteID = siteID
                                val noteID = noteID
                                val noteIDs = arrayOf(noteID)
                                val title = targetComputing.title
                                val artist = targetComputing.artist
                                val genre = targetComputing.genre
                                val levelText = targetComputing.levelText
                                val level = targetComputing.level
                                val wantLevelID = targetComputing.wantLevelID
                                val judgmentStage = targetComputing.judgmentStage
                                val hitPointsValue = targetComputing.hitPointsValue
                                val totalNotes = targetComputing.totalNotes
                                val longNotes = targetComputing.longNotes
                                val autoableNotes = targetComputing.autoableNotes
                                val trapNotes = targetComputing.trapNotes
                                val highestInputCount = targetComputing.highestInputCount
                                val length = targetComputing.length
                                val bpm = targetComputing.bpm
                                val lowestBPM = targetComputing.lowestBPM
                                val highestBPM = targetComputing.highestBPM
                                val inputMode = targetComputing.inputMode
                            }
                        )
                        send(
                            EventOuterClass.Event.EventID.SET_SITE_NAME,
                            object {
                                val siteID = siteID
                                val siteName = targetComputing.title
                            }
                        )
                    }
                }
            }
        }
    }

    override fun logInfo(toNotify: String) {
        LoggerFactory.getLogger(javaClass).info("[{}] {}", this.qwilightName, toNotify)
    }

    override fun logFault(e: Throwable) {
        LoggerFactory.getLogger(javaClass).error("[{}] {}", this.qwilightName, Utility.getFaultText(e))
    }

    override fun channelActive(ctx: ChannelHandlerContext) {
        handler = ctx
        AwilightHandler.putAvatar(ctx, this)
    }

    override fun channelInactive(ctx: ChannelHandlerContext) {
        AwilightHandler.quitAvatar(ctx)
    }

    public override fun channelRead0(ctx: ChannelHandlerContext, msg: EventOuterClass.Event) {
        val eventText = msg.text
        val jm = ObjectMapper()
        when (msg.eventID) {
            EventOuterClass.Event.EventID.ESTABLISH -> {
                val text = jm.readValue(eventText, JSON.TwilightEstablish::class.java)
                this.avatarID = text.avatarID
                on("onEstablished", emptyArray())
                send(
                    EventOuterClass.Event.EventID.ENTER_SITE,
                    object {
                        val siteID = "00000000-0000-0000-0000-000000000000"
                        val siteCipher = ""
                    }
                )
                send(
                    EventOuterClass.Event.EventID.ENTER_SITE,
                    object {
                        val siteID = "00000000-0000-0000-0000-000000000001"
                        val siteCipher = ""
                    }
                )
                WitSystem.handleLoop(this, LocalDateTime.now(), -1) {
                    synchronized(enteredSites) {
                        val sites = enteredSites.values.filter { it.isNetSite }
                        if (sites.isNotEmpty()) {
                            sites.stream()
                                .filter { it.siteSituation == AwilightSite.SITE_SITUATION_DEFAULT }
                                .findAny().ifPresent {
                                    if (Arrays.stream(it.noteIDs)
                                            .anyMatch { noteID -> NoteFilesSystem.hasNoteFile(noteID) }
                                    ) {
                                        if (it.isNetAllowed) {
                                            send(EventOuterClass.Event.EventID.LEVY_NET, it.siteID)
                                        }
                                    } else {
                                        setNoteFile(it.siteID)
                                    }
                                }
                        } else {
                            logValueFuture {
                                HttpClients.createDefault().use {
                                    val dataGet = HttpGet(Configure.www.qwilight + "/sites")
                                    val siteIDs =
                                        ObjectMapper().readValue(
                                            it.execute(
                                                dataGet,
                                                BasicHttpClientResponseHandler()
                                            ), Array<JSON.TwilightWwwSite>::class.java
                                        )
                                            .filter { siteData -> !siteData.hasCipher && siteData.siteConfigure == 2 && siteData.avatarCount < 7 }
                                            .map { siteData -> siteData.siteID }
                                    if (siteIDs.isNotEmpty()) {
                                        send(
                                            EventOuterClass.Event.EventID.ENTER_SITE,
                                            object {
                                                val siteID = siteIDs.random()
                                                val siteCipher = ""
                                            }
                                        )
                                    } else {
                                        NoteFilesSystem.noteFile?.let { noteFile ->
                                            val targetComputing =
                                                BaseCompiler.handleCompile(Files.readAllBytes(noteFile.value), -1)
                                                    .random()
                                            val modeComponentValue = ModeComponent()
                                            send(
                                                EventOuterClass.Event.EventID.NEW_SITE,
                                                object {
                                                    val siteName = targetComputing.title
                                                    val siteCipher = ""

                                                    @JvmField
                                                    val isNetSite = true
                                                    val data = object {
                                                        val salt = modeComponentValue.salt
                                                        val valueMultiplier = modeComponentValue.multiplierValue
                                                        val autoMode = modeComponentValue.autoMode
                                                        val noteSaltMode = modeComponentValue.noteSaltMode
                                                        val faintNoteMode = modeComponentValue.faintNoteMode
                                                        val judgmentMode = modeComponentValue.judgmentMode
                                                        val hitPointsMode = modeComponentValue.hitPointsMode
                                                        val noteMobilityMode = modeComponentValue.noteMobilityMode
                                                        val longNoteMode = modeComponentValue.longNoteMode
                                                        val inputFavorMode = modeComponentValue.inputFavorMode
                                                        val noteModifyMode = modeComponentValue.noteModifyMode
                                                        val bpmMode = modeComponentValue.bpmMode
                                                        val waveMode = modeComponentValue.waveMode
                                                        val setNoteMode = modeComponentValue.setNoteMode
                                                        val lowestJudgmentConditionMode =
                                                            modeComponentValue.lowestJudgmentConditionMode
                                                        val audioMultiplier = modeComponentValue.audioMultiplier
                                                        val highestJudgment0 = -1.0
                                                        val higherJudgment0 = -1.0
                                                        val highJudgment0 = -1.0
                                                        val lowJudgment0 = -1.0
                                                        val lowerJudgment0 = -1.0
                                                        val lowestJudgment0 = -1.0
                                                        val highestJudgment1 = 1.0
                                                        val higherJudgment1 = 1.0
                                                        val highJudgment1 = 1.0
                                                        val lowJudgment1 = 1.0
                                                        val lowerJudgment1 = 1.0
                                                        val lowestJudgment1 = 1.0
                                                        val lowestLongNoteModify = 100.0
                                                        val highestLongNoteModify = 100.0
                                                        val putNoteSet = 25.0
                                                        val putNoteSetMillis = 100.0
                                                    }
                                                    val noteID = targetComputing.noteID
                                                    val noteIDs = arrayOf(targetComputing.noteID)
                                                    val title = targetComputing.title
                                                    val artist = targetComputing.artist
                                                    val genre = targetComputing.genre
                                                    val levelText = targetComputing.levelText
                                                    val level = targetComputing.level
                                                    val wantLevelID = targetComputing.wantLevelID
                                                    val judgmentStage = targetComputing.judgmentStage
                                                    val hitPointsValue = targetComputing.hitPointsValue
                                                    val totalNotes = targetComputing.totalNotes
                                                    val longNotes = targetComputing.longNotes
                                                    val autoableNotes = targetComputing.autoableNotes
                                                    val trapNotes = targetComputing.trapNotes
                                                    val highestInputCount = targetComputing.highestInputCount
                                                    val length = targetComputing.length
                                                    val bpm = targetComputing.bpm
                                                    val inputMode = targetComputing.inputMode

                                                    @JvmField
                                                    val isAutoLongNote = targetComputing.isAutoLongNote
                                                    val bundleEntryPath = ""
                                                }
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            EventOuterClass.Event.EventID.ENTER_SITE -> {
                val text = jm.readValue(eventText, JSON.TwilightEnterSite::class.java)
                synchronized(enteredSites) {
                    enteredSites.put(
                        text.siteID,
                        AwilightSite(
                            text.siteID,
                            text.isNetSite,
                            text.noteID,
                            text.noteIDs
                        )
                    )
                }
            }

            EventOuterClass.Event.EventID.QUIT_SITE -> {
                synchronized(enteredSites) { enteredSites.remove(eventText) }
            }

            EventOuterClass.Event.EventID.LEVY_NET -> {
                val text = jm.readValue(eventText, JSON.TwilightLevyNet::class.java)
                synchronized(enteredSites) {
                    enteredSites[text.siteID]?.let {
                        synchronized(defaultComputerCSX) {
                            defaultComputer?.stop()
                            defaultComputer = DefaultCompute(
                                if (NoteFilesSystem.hasNoteFile(it.noteID)) {
                                    it.noteID
                                } else {
                                    Arrays.stream(text.noteIDs)
                                        .filter {
                                            NoteFilesSystem.hasNoteFile(it)
                                        }.findAny().orElse("")
                                }, this, text.handlerID, it
                            ).also { tmpComputer ->
                                Thread {
                                    var isOK = false
                                    try {
                                        if (tmpComputer.noteID.isNotEmpty()) {
                                            NoteFilesSystem.getNoteFile(tmpComputer.noteID)?.let {
                                                tmpComputer.handleCompile(it)
                                                isOK = true
                                            }
                                        }
                                    } finally {
                                        if (!isOK) {
                                            tmpComputer.stop()
                                            setNoteFile(it.siteID)
                                        }
                                    }
                                }.apply { isDaemon = true }.start()
                            }
                        }
                    }
                }
            }

            EventOuterClass.Event.EventID.COMPILED -> {
                synchronized(defaultComputerCSX) {
                    if (eventText == defaultComputer?.handlerID) {
                        Thread {
                            try {
                                defaultComputer?.handleNotes(w)
                            } finally {
                                defaultComputer?.stop()
                            }
                        }.apply { isDaemon = true }.start()
                    }
                }
            }

            EventOuterClass.Event.EventID.QUIT_NET -> {
                val text = jm.readValue(eventText, JSON.TwilightQuitNet::class.java)
                synchronized(defaultComputerCSX) {
                    defaultComputer?.let { tmpComputer ->
                        if (tmpComputer.handlerID == text.handlerID) {
                            tmpComputer.stop()
                            text.quitNetItems?.let {
                                on(
                                    "onHandled", arrayOf(
                                        Py.newBoolean(
                                            tmpComputer.isF
                                        ),
                                        Py.newUnicode(tmpComputer.title),
                                        Py.newInteger(tmpComputer.targetStand)
                                    )
                                )
                            }
                            setNoteFile(tmpComputer.site.siteID)
                        }
                    }
                }
            }

            EventOuterClass.Event.EventID.CALL_SITE_AVATAR -> {
                val text = jm.readValue(
                    eventText,
                    JSON.TwilightCallSiteAvatar::class.java
                )
                synchronized(enteredSites) {
                    enteredSites[text.siteID]?.let { site ->
                        Arrays.stream(text.data)
                            .filter { it.avatarID == avatarID }
                            .findAny().ifPresent {
                                site.setAvatarConfigure(
                                    avatarID == text.siteHand,
                                    it.avatarConfigure
                                )
                            }
                        site.setSIteSituation(text.situationValue)
                        site.setNetAllowed(text.data, avatarID)
                    }
                }
            }

            EventOuterClass.Event.EventID.CALL_SITE_NET -> {
                val text = jm.readValue(eventText, JSON.TwilightCallSiteNet::class.java)
                synchronized(enteredSites) {
                    enteredSites[text.siteID]?.setSiteNetData(text)
                }
            }

            EventOuterClass.Event.EventID.SITE_YELL -> {
                val text = jm.readValue(eventText, JSON.TwilightSiteYell::class.java)
                on(
                    "onSiteYell", arrayOf(
                        Py.newBoolean(
                            avatarID == text.target
                        ),
                        Py.newUnicode(text.siteID),
                        Py.newUnicode(text.avatarID),
                        Py.newUnicode(text.avatarName),
                        Py.newUnicode(text.siteYell)
                    )
                )
            }

            EventOuterClass.Event.EventID.CALL_IO -> {
                val text = jm.readValue(eventText, JSON.TwilightCallIO::class.java)
                synchronized(defaultComputerCSX) {
                    defaultComputer?.let { tmpComputer ->
                        if (tmpComputer.setStop) {
                            send(EventOuterClass.Event.EventID.IO_NOT, object {
                                val avatarID = text.avatarID
                                val handlerID = text.handlerID
                            })
                        } else {
                            val modeComponentValue = tmpComputer.modeComponentValue
                            send(
                                EventOuterClass.Event.EventID.CALL_IO_COMPONENT,
                                object {
                                    val noteID = tmpComputer.noteID
                                    val handlerID = tmpComputer.handlerID
                                    val avatarID = text.avatarID
                                    val data = object {
                                        val salt = modeComponentValue.salt
                                        val valueMultiplier = modeComponentValue.multiplierValue
                                        val autoMode = modeComponentValue.autoMode
                                        val noteSaltMode = modeComponentValue.noteSaltMode
                                        val faintNoteMode = modeComponentValue.faintNoteMode
                                        val judgmentMode = modeComponentValue.judgmentMode
                                        val hitPointsMode = modeComponentValue.hitPointsMode
                                        val noteMobilityMode = modeComponentValue.noteMobilityMode
                                        val longNoteMode = modeComponentValue.longNoteMode
                                        val inputFavorMode = modeComponentValue.inputFavorMode
                                        val noteModifyMode = modeComponentValue.noteModifyMode
                                        val bpmMode = modeComponentValue.bpmMode
                                        val waveMode = modeComponentValue.waveMode
                                        val setNoteMode = modeComponentValue.setNoteMode
                                        val lowestJudgmentConditionMode = modeComponentValue.lowestJudgmentConditionMode
                                        val audioMultiplier = modeComponentValue.audioMultiplier
                                        val highestJudgment0 = -1.0
                                        val higherJudgment0 = -1.0
                                        val highJudgment0 = -1.0
                                        val lowJudgment0 = -1.0
                                        val lowerJudgment0 = -1.0
                                        val lowestJudgment0 = -1.0
                                        val highestJudgment1 = 1.0
                                        val higherJudgment1 = 1.0
                                        val highJudgment1 = 1.0
                                        val lowJudgment1 = 1.0
                                        val lowerJudgment1 = 1.0
                                        val lowestJudgment1 = 1.0
                                        val lowestLongNoteModify = 100.0
                                        val highestLongNoteModify = 100.0
                                        val putNoteSet = 25.0
                                        val putNoteSetMillis = 100.0
                                    }
                                    val ioHandlerID = text.handlerID

                                    @JvmField
                                    val isFailMode = true
                                    val ioMillis = text.ioMillis
                                    val targetIOMillis = System.currentTimeMillis()
                                }
                            )
                        }
                    }
                }
            }

            EventOuterClass.Event.EventID.COMPILED_IO -> {
                val text = jm.readValue(eventText, JSON.TwilightCompiledIO::class.java)
                synchronized(defaultComputerCSX) {
                    defaultComputer?.let { tmpComputer ->
                        if (text.handlerID == tmpComputer.handlerID) {
                            if (tmpComputer.setStop) {
                                send(
                                    EventOuterClass.Event.EventID.IO_QUIT,
                                    object {
                                        val handlerID = text.handlerID
                                        val avatarIDs = arrayOf(text.avatarID)

                                        @JvmField
                                        val isBanned = true
                                    }
                                )
                            } else {
                                tmpComputer.twilightCompiledIOQueue.offer(text)
                            }
                        }
                    }
                }
            }

            else -> Unit
        }
    }

    override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
        logFault(cause)
    }
}