package net.taehui.twilight

import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import net.dv8tion.jda.api.entities.Member
import net.dv8tion.jda.api.events.message.MessageDeleteEvent
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.events.message.MessageUpdateEvent
import net.taehui.twilight.system.*
import okhttp3.internal.toImmutableList
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.*
import java.util.stream.Collectors
import java.util.stream.Stream
import kotlin.random.Random

class Site : Logger {
    enum class AvatarConfigure(val value: Int) {
        DEFAULT(0), LEVYING(2), COMPILING(3), NET(4)
    }

    enum class SiteSituation {
        DEFAULT, COMPILING, NET;

        @JsonValue
        fun getValue(): Int {
            return ordinal
        }
    }

    enum class SiteMode {
        DEFAULT, FAVOR, NET;

        @JsonValue
        fun getValue(): Int {
            return ordinal
        }
    }

    private val isEssential: Boolean
    private val siteMode: SiteMode
    private val avatarsCeiling: Int
    private val siteID: UUID
    private val siteCipher: String
    private val isEditable: Boolean
    private val avatarsCSX = Any()
    private val avatars = mutableListOf<Avatar>()
    private val avatarConfigures = mutableMapOf<String, AvatarConfigure>()
    private val avatarGroups = mutableMapOf<String, Int>()
    private val lastAudioInputMillis = mutableMapOf<String, Long>()
    private val isAudioInputs = mutableMapOf<String, Boolean>()
    private val siteHandQueue = LinkedList<Avatar>()
    private val netSIteCommentItems = object : LinkedList<NetSiteCommentItems>() {
        override fun add(element: NetSiteCommentItems): Boolean {
            if (size >= 10) {
                remove()
            }
            return super.add(element)
        }
    }
    private val siteYells = LinkedList<SiteYell>()
    private val platformSiteYells = mutableMapOf<Long, MutableList<SiteYell>>()
    private val targetComputing = Computing("")
    private val isCallable: Boolean
    private val isGetNotify: Boolean
    private val isEnterQuitAware: Boolean
    private val hasAvatar: Boolean
    private val handler: ScheduledFuture<*>?
    private val netItems = ConcurrentHashMap<String, NetItem>()
    private var netDataMillis = 0L
    private var siteHand: Avatar? = null
    private var siteName = ""
    private var handlerID = ""
    private var siteNotify = ""
    private var isFavorNoteFile = true
    private var isFavorModeComponent = true
    private var isFavorAudioMultiplier = false
    private var isAutoSiteHand = false
    private var modeComponentData: Any? = null
    private var siteSituation = SiteSituation.DEFAULT
    private var validHunterMode = 0
    private var validNetMode = 0
    private var allowedPostableItems = emptyArray<Int>()
    private var noteID = ""
    private var noteIDs: Array<String>? = null
    private var bundleEntryPath = ""
    private var bundleName = ""

    constructor(
        siteID: UUID,
        isNetSite: Boolean,
        siteName: String,
        siteCipher: String,
        siteHand: Avatar?,
        isCallable: Boolean
    ) {
        siteMode = if (isNetSite) SiteMode.NET else SiteMode.FAVOR
        avatarsCeiling = if (isNetSite) 8 else Int.MAX_VALUE
        this.siteID = siteID
        this.siteName = siteName
        this.siteCipher = siteCipher
        isEditable = true
        this.siteHand = siteHand
        this.isCallable = isCallable
        isGetNotify = true
        isEnterQuitAware = true
        isEssential = false
        hasAvatar = true
        handler = ses.scheduleAtFixedRate({
            var wasModified = false
            val millis = System.currentTimeMillis()
            synchronized(avatarsCSX) {
                lastAudioInputMillis.forEach {
                    val isAudioInput = it.value + 500 >= millis
                    if (isAudioInputs[it.key] != isAudioInput) {
                        isAudioInputs[it.key] = isAudioInput
                        wasModified = true
                    }
                }
            }
            if (wasModified) {
                doCallSiteAvatar(false)
            }
        }, 0L, (1000 / 60).toLong(), TimeUnit.MILLISECONDS)
    }

    constructor(
        siteID: UUID,
        siteName: String,
        isEditable: Boolean,
        isGetNotify: Boolean,
        isEssential: Boolean,
        hasAvatar: Boolean
    ) {
        siteMode = SiteMode.DEFAULT
        avatarsCeiling = Int.MAX_VALUE
        this.siteID = siteID
        this.siteName = siteName
        siteCipher = ""
        this.isEditable = isEditable
        isCallable = true
        this.isGetNotify = isGetNotify
        isEnterQuitAware = false
        this.isEssential = isEssential
        this.hasAvatar = hasAvatar
        handler = null
    }

    fun onWipeSite() {
        if (siteMode == SiteMode.NET) {
            FileUtils.deleteQuietly(TwilightComponent.BUNDLE_ENTRY_PATH.resolve("$siteID.zip").toFile())
        }
        handler?.cancel(false)
    }

    fun isAvatarJoined(avatar: Avatar): Boolean {
        return avatars.contains(avatar)
    }

    private fun tryQuitNet() {
        val netAvatars = avatars.filter { avatarConfigures[it.avatarID] == AvatarConfigure.NET }
        if (netAvatars.all { netItems[it.avatarID]?.avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default }) {
            netAvatars.forEach {
                avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT
            }
            siteSituation = SiteSituation.DEFAULT
            setNetBundle("")
            if (isAutoSiteHand) {
                setAutoSiteHand()
            }
            doCallSiteAvatar(false)
            val netItemsHaveComment = netItems.entries.stream().filter { it.value.comment != null }
                .collect(
                    Collectors.toMap({ it.key }, { it.value })
                )
            val validNetItems = setValidMap(netItemsHaveComment).toList()

            val text = object {
                val handlerID = this@Site.handlerID
                val quitNetItems = validNetItems.map {
                    object {
                        val avatarID = it.avatarID
                        val avatarName = it.avatarName
                        val title = it.title
                        val artist = it.artist
                        val genre = it.genre
                        val level = it.level
                        val levelText = it.levelText
                        val wantLevelID = it.wantLevelID
                        val autoMode = it.autoMode
                        val noteSaltMode = it.noteSaltMode
                        val audioMultiplier = it.audioMultiplier
                        val faintNoteMode = it.faintNoteMode
                        val hitPointsMode = it.hitPointsMode
                        val judgmentMode = it.judgmentMode
                        val noteMobilityMode = it.noteMobilityMode
                        val inputFavorMode = it.inputFavorMode
                        val longNoteMode = it.longNoteMode
                        val noteModifyMode = it.noteModifyMode
                        val bpmMode = it.bpmMode
                        val waveMode = it.waveMode
                        val setNoteMode = it.setNoteMode
                        val lowestJudgmentConditionMode = it.lowestJudgmentConditionMode
                        val totalNotes = it.totalNotes
                        val judgmentStage = it.judgmentStage
                        val hitPointsValue = it.hitPointsValue
                        val highestInputCount = it.highestInputCount
                        val length = it.length
                        val bpm = it.bpm
                        val multiplier = it.multiplier
                        val inputMode = it.inputMode
                        val stand = it.stand
                        val highestBand = it.highestBand
                        val point = it.point
                        val hitPoints = it.hitPoints
                        val highestJudgment0 = it.highestJudgment0
                        val higherJudgment0 = it.higherJudgment0
                        val highJudgment0 = it.highJudgment0
                        val lowJudgment0 = it.lowJudgment0
                        val lowerJudgment0 = it.lowerJudgment0
                        val lowestJudgment0 = it.lowestJudgment0
                        val highestJudgment1 = it.highestJudgment1
                        val higherJudgment1 = it.higherJudgment1
                        val highJudgment1 = it.highJudgment1
                        val lowJudgment1 = it.lowJudgment1
                        val lowerJudgment1 = it.lowerJudgment1
                        val lowestJudgment1 = it.lowestJudgment1

                        @JvmField
                        val isF = it.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed

                        val netPosition = validNetItems.indexOf(it)
                    }
                }.toList()
            }
            val comments = validNetItems.map { it.comment?.toByteString() }.toTypedArray()
            netAvatars.forEach {
                it.send(
                    EventOuterClass.Event.EventID.QUIT_NET, text, *comments
                )
            }
            if (netItemsHaveComment.isNotEmpty()) {
                netSIteCommentItems.add(NetSiteCommentItems(netItemsHaveComment, System.currentTimeMillis()))
            }
        }
    }

    private fun setValidMap(netItems: Map<String, NetItem>): Stream<NetItem> {
        fun getAvatarGroup(netItem: NetItem): String {
            val avatarID = netItem.avatarID
            val avatarGroup = avatarGroups[avatarID] ?: 0
            return if (avatarGroup > 0) "#${avatarGroup}" else avatarID
        }

        fun getValue(netItem: NetItem): Double {
            return when (validHunterMode) {
                NetItem.STAND_MODE -> netItem.stand.toDouble()
                NetItem.POINT_MODE -> netItem.point
                NetItem.BAND_MODE -> netItem.highestBand.toDouble()
                else -> 0.0
            }
        }

        val avatarGroupValues = netItems.values.groupBy { getAvatarGroup(it) }
            .map {
                Pair(it.key, it.value.map { netItem ->
                    getValue(netItem)
                }.average())
            }.toMap()

        fun getAvatarGroupValue(avatarGroup: String): Double {
            return avatarGroupValues[avatarGroup] ?: 0.0
        }

        return netItems.values.stream().sorted { o1, o2 ->
            val o1Failed = o1.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed
            val o2Failed = o2.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Failed
            if (o1Failed xor o2Failed) {
                o1Failed.compareTo(o2Failed)
            } else {
                val avatarGroup1 = getAvatarGroup(o1)
                val avatarGroup2 = getAvatarGroup(o2)
                if (avatarGroup1 != avatarGroup2) {
                    getAvatarGroupValue(avatarGroup2).compareTo(getAvatarGroupValue(avatarGroup1))
                } else {
                    getValue(o2).compareTo(getValue(o1))
                }
            }
        }
    }

    fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString) {
        synchronized(avatarsCSX) {
            avatars.forEach {
                it.send(
                    eventID, text, *data
                )
            }
        }
    }

    fun send(event: EventOuterClass.Event) {
        synchronized(avatarsCSX) {
            avatars.forEach {
                it.send(event)
            }
        }
    }

    private fun sendSiteYell(twilightSiteYell: JSON.TwilightSiteYell) {
        synchronized(avatarsCSX) {
            avatars.forEach {
                it.send(
                    EventOuterClass.Event.EventID.SITE_YELL, twilightSiteYell
                )
            }
        }
        PlatformSystem.sendSiteYell(twilightSiteYell)
    }

    private fun sendTranslatedSiteYell(twilightSiteYell: JSON.TwilightSiteYell) {
        synchronized(avatarsCSX) {
            avatars.forEach { avatar ->
                avatar.send(
                    EventOuterClass.Event.EventID.SITE_YELL, twilightSiteYell
                )
                Translator.translate(avatar.language, twilightSiteYell.siteYell, avatar).thenAccept {
                    avatar.send(
                        EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object {
                            val siteID = this@Site.siteID.toString()
                            val siteYellID = twilightSiteYell.siteYellID
                            val siteYell = it
                        })
                }
            }
        }
        PlatformSystem.sendSiteYell(twilightSiteYell)
    }

    private fun doCallSiteAvatar(setNoteFile: Boolean) {
        if (hasAvatar) {
            synchronized(avatarsCSX) {
                send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, object {
                    val siteID = this@Site.siteID.toString()
                    val siteName = this@Site.siteName
                    val siteHand = this@Site.siteHand?.avatarID ?: ""
                    val situationValue = siteSituation
                    val setNoteFile = setNoteFile
                    val data = synchronized(avatarsCSX) {
                        avatars.map {
                            object {
                                val avatarID = it.avatarID
                                val avatarConfigure = (avatarConfigures[it.avatarID] ?: AvatarConfigure.DEFAULT).value
                                val avatarName = it.avatarName
                                val avatarGroup = avatarGroups[it.avatarID] ?: -1

                                @JvmField
                                val isValve = it.isValve

                                @JvmField
                                val isAudioInput = isAudioInputs[it.avatarID] ?: false
                            }
                        }
                    }
                })
            }
        }
    }

    fun doCallSiteAvatar(avatar: Avatar?, avatars: List<Member>) {
        val data = object {
            val siteID = this@Site.siteID.toString()
            val siteName = this@Site.siteName
            val siteHand = ""
            val situationValue = SiteSituation.DEFAULT
            val setNoteFile = false
            val data = synchronized(avatarsCSX) {
                avatars.map {
                    object {
                        val avatarID = "$${it.id}"
                        val avatarConfigure = AvatarConfigure.DEFAULT.value
                        val avatarName = it.effectiveName

                        @JvmField
                        val isValve = false

                        @JvmField
                        val isAudioInput = false
                    }
                }
            }
        }
        avatar?.send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, data)
            ?: send(EventOuterClass.Event.EventID.CALL_SITE_AVATAR, data)
    }

    private fun doCallSiteNet() {
        send(EventOuterClass.Event.EventID.CALL_SITE_NET, object {
            val siteID = this@Site.siteID.toString()
            val noteID = this@Site.noteID
            val noteIDs = this@Site.noteIDs
            val title = targetComputing.title
            val artist = targetComputing.artist
            val levelText = targetComputing.levelText
            val level = targetComputing.level
            val wantLevelID = targetComputing.wantLevelID
            val genre = targetComputing.genre
            val bpm = targetComputing.bpm
            val lowestBPM = targetComputing.lowestBPM
            val highestBPM = targetComputing.highestBPM
            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 inputMode = targetComputing.inputMode

            @JvmField
            val isAutoLongNote = targetComputing.isAutoLongNote

            @JvmField
            val isFavorNoteFile = this@Site.isFavorNoteFile

            @JvmField
            val isFavorModeComponent = this@Site.isFavorModeComponent

            @JvmField
            val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier

            @JvmField
            val isAutoSiteHand = this@Site.isAutoSiteHand

            val validHunterMode = this@Site.validHunterMode
            val validNetMode = this@Site.validNetMode
            val allowedPostableItems = this@Site.allowedPostableItems
            val bundleEntryPath = this@Site.bundleEntryPath
            val bundleName = this@Site.bundleName
        })
    }

    private fun doCallSiteModeComponent() {
        send(EventOuterClass.Event.EventID.CALL_SITE_MODE_COMPONENT, object {
            val siteID = this@Site.siteID.toString()
            val modeComponentData = this@Site.modeComponentData
        })
    }

    fun setLevying(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && siteSituation == SiteSituation.DEFAULT) {
                if (siteHand == avatar && validNetMode > 0 && allowedPostableItems.isEmpty()) {
                    send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongAllowedPostableItems"))
                } else {
                    val avatarID = avatar.avatarID
                    when (avatarConfigures[avatarID]) {
                        AvatarConfigure.LEVYING -> {
                            avatarConfigures.replace(avatarID, AvatarConfigure.DEFAULT)
                            doCallSiteAvatar(false)
                        }

                        AvatarConfigure.DEFAULT -> {
                            avatarConfigures.replace(avatarID, AvatarConfigure.LEVYING)
                            doCallSiteAvatar(false)
                        }

                        else -> Unit
                    }
                    if (siteHand == avatar) {
                        when (avatarConfigures[avatarID]) {
                            AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT -> {
                                siteSituation = SiteSituation.COMPILING
                                handlerID = UUID.randomUUID().toString()
                                netItems.clear()
                                val avatarsCount = avatars.count {
                                    avatarConfigures[it.avatarID] == AvatarConfigure.LEVYING
                                }
                                netItems.putAll(avatars.filter {
                                    if (avatarConfigures.replace(
                                            it.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.COMPILING
                                        )
                                    ) {
                                        it.send(EventOuterClass.Event.EventID.LEVY_NET, object {
                                            val siteID = this@Site.siteID.toString()
                                            val noteIDs = this@Site.noteIDs
                                            val handlerID = this@Site.handlerID

                                            @JvmField
                                            val isSiteHand = this@Site.siteHand == it

                                            @JvmField
                                            val isFavorModeComponent = this@Site.isFavorModeComponent

                                            @JvmField
                                            val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier

                                            val validHunterMode = this@Site.validHunterMode
                                            val validNetMode = this@Site.validNetMode
                                            val allowedPostableItems = this@Site.allowedPostableItems

                                            val modeComponentData = this@Site.modeComponentData

                                            val avatarsCount = avatarsCount
                                        })
                                        true
                                    } else {
                                        false
                                    }
                                }.map {
                                    Pair(it.avatarID, NetItem(it.avatarID, it.avatarName))
                                })
                                doCallSiteAvatar(false)
                            }

                            else -> Unit
                        }
                    }
                }
            }
        }
    }

    fun stopSiteNet(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation != SiteSituation.DEFAULT) {
                synchronized(avatarsCSX) {
                    avatars.filter {
                        val avatarConfigure = avatarConfigures[it.avatarID]
                        avatarConfigure == AvatarConfigure.COMPILING || avatarConfigure == AvatarConfigure.NET
                    }.forEach {
                        avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT
                        it.send(
                            EventOuterClass.Event.EventID.QUIT_NET, object {
                                val handlerID = this@Site.handlerID
                            }
                        )
                    }
                }
                siteSituation = SiteSituation.DEFAULT
                doCallSiteAvatar(true)
            }
        }
    }

    fun setCompiled(avatar: Avatar, isCompiled: Boolean, handlerID: String) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && this.handlerID == handlerID && siteSituation != SiteSituation.DEFAULT) {
                val avatarID = avatar.avatarID
                if (isCompiled) {
                    avatarConfigures.replace(avatarID, AvatarConfigure.COMPILING, AvatarConfigure.NET)
                } else {
                    when (avatarConfigures[avatarID]) {
                        AvatarConfigure.COMPILING, AvatarConfigure.NET -> {
                            avatarConfigures[avatarID] = AvatarConfigure.DEFAULT
                        }

                        else -> Unit
                    }
                    netItems[avatarID]?.avatarNetStatus = EventOuterClass.Event.AvatarNetStatus.Failed
                }

                when (siteSituation) {
                    SiteSituation.COMPILING -> {
                        if (avatars.all {
                                val avatarConfigure = avatarConfigures[it.avatarID]
                                avatarConfigure == AvatarConfigure.DEFAULT || avatarConfigure == AvatarConfigure.LEVYING
                            }) {
                            siteSituation = SiteSituation.DEFAULT
                        } else if (avatars.all { avatarConfigures[it.avatarID] != AvatarConfigure.COMPILING }) {
                            siteSituation = SiteSituation.NET
                            avatars.filter { avatarConfigures[it.avatarID] == AvatarConfigure.NET }.forEach {
                                it.send(
                                    EventOuterClass.Event.EventID.COMPILED, this.handlerID
                                )
                            }
                        }
                    }

                    SiteSituation.NET -> {
                        tryQuitNet()
                    }

                    else -> Unit
                }

                doCallSiteAvatar(false)
            }
        }
    }

    fun setNetData(
        avatar: Avatar,
        qwilightCallNet: EventOuterClass.Event.QwilightCallNet,
        comment: CommentOuterClass.Comment?
    ) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && handlerID == qwilightCallNet.handlerID && siteSituation == SiteSituation.NET) {
                val avatarID = avatar.avatarID
                if (avatarConfigures[avatarID] == AvatarConfigure.NET) {
                    val targetNetItem = netItems[avatarID]!!
                    if (targetNetItem.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Default) {
                        targetNetItem.setValues(qwilightCallNet, comment)
                        val avatarNetStatus = qwilightCallNet.avatarNetStatus
                        targetNetItem.avatarNetStatus = avatarNetStatus
                        if (avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default) {
                            tryQuitNet()
                        } else {
                            val millis = System.currentTimeMillis()
                            if (millis - netDataMillis > 1000.0 / 60) {
                                netDataMillis = millis
                                netItems.values.forEach { it.setFailed() }
                                send(EventOuterClass.Event.newBuilder().apply {
                                    this.eventID = EventOuterClass.Event.EventID.CALL_NET
                                    this.twilightCallNet = EventOuterClass.Event.TwilightCallNet.newBuilder().apply {
                                        this.handlerID = this@Site.handlerID
                                        var targetPosition = 0
                                        this.addAllData(setValidMap(netItems).map {
                                            EventOuterClass.Event.TwilightCallNet.CallNetItem.newBuilder().apply {
                                                this.avatarNetStatus = it.avatarNetStatus
                                                this.avatarID = it.avatarID
                                                this.avatarName = it.avatarName
                                                this.stand = it.stand
                                                this.band = it.highestBand
                                                this.point = it.point
                                                this.hitPoints = it.hitPoints
                                                this.isFailed = it.failed
                                                this.lastJudged = it.lastJudged
                                                this.addAllDrawings(it.drawings)
                                                this.drawingComponent = it.drawingComponent
                                                this.hitPointsMode = it.hitPointsMode
                                                this.targetPosition = targetPosition++
                                            }.build()
                                        }.toList())
                                    }.build()
                                }.build())
                            }
                        }
                    }
                }
            }
        }
    }

    fun getCallingData(): Any? {
        return if (isCallable) object {
            val siteID = this@Site.siteID.toString()
            val siteName = this@Site.siteName
            val siteSituation = this@Site.siteSituation
            val siteConfigure = siteMode
            val hasCipher = siteCipher.isNotEmpty()
            val avatarCount = (if (hasAvatar) avatars else PlatformSystem.platformAvatars).size
        } else null
    }

    fun isNetSiteHand(avatar: Avatar): Boolean {
        return synchronized(avatarsCSX) {
            siteMode == SiteMode.NET && avatar == siteHand
        }
    }

    fun quitAvatar(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (avatars.remove(avatar)) {
                val avatarID = avatar.avatarID
                lastAudioInputMillis.remove(avatarID)
                isAudioInputs.remove(avatarID)
                val avatarConfigure = avatarConfigures.remove(avatarID)
                if (avatarConfigure == AvatarConfigure.NET || avatarConfigure == AvatarConfigure.COMPILING) {
                    netItems.remove(avatarID)
                    avatar.send(EventOuterClass.Event.EventID.QUIT_NET, object {
                        val handlerID = this@Site.handlerID
                    })
                    tryQuitNet()
                }
                avatarGroups.remove(avatarID)
                if (siteHandQueue.remove(avatar)) {
                    if (siteHand == avatar) {
                        setAutoSiteHand()
                    }
                }
                avatar.send(EventOuterClass.Event.EventID.QUIT_SITE, siteID.toString())
                if (isEnterQuitAware && avatar.isEnterQuitAware) {
                    putSiteYell("@Quit", avatarID, avatar.avatarName, System.currentTimeMillis())
                }
                doCallSiteAvatar(true)
            }
        }
    }

    val isVoid: Boolean
        get() {
            return !isEssential && synchronized(avatarsCSX) {
                avatars.isEmpty()
            }
        }

    fun quitAvatars(): Boolean {
        synchronized(avatarsCSX) {
            avatars.toImmutableList().forEach {
                quitAvatar(it)
            }
        }
        return !isEssential
    }

    fun enterSite(avatar: Avatar) {
        synchronized(avatarsCSX) {
            avatars.add(avatar)
            val isNetSite = siteMode == SiteMode.NET
            val avatarID = avatar.avatarID
            avatarConfigures[avatarID] = AvatarConfigure.DEFAULT
            avatarGroups[avatarID] = if (isNetSite) 0 else -1
            lastAudioInputMillis[avatarID] = Long.MIN_VALUE
            isAudioInputs[avatarID] = false
            siteHandQueue.offerFirst(avatar)
            val data = synchronized(siteYells) {
                siteYells.stream().skip(0.coerceAtLeast(siteYells.size - SITE_YELL_UNIT).toLong()).map {
                    val avatarName = it.avatarName
                    val isWiped = it.isWiped
                    object {
                        val avatarID = it.avatarID
                        val avatarName = if (isWiped) "@Wiped" else avatarName
                        val date = it.date
                        val siteYell = if (isWiped) avatarName else it.siteYell
                        val siteYellID = it.siteYellID
                    }
                }.toList()
            }
            avatar.send(EventOuterClass.Event.EventID.ENTER_SITE, object {
                val siteID = this@Site.siteID.toString()
                val siteNotify = this@Site.siteNotify

                @JvmField
                val isNetSite = isNetSite

                @JvmField
                val isGetNotify = this@Site.isGetNotify

                @JvmField
                val isEditable = this@Site.isEditable

                @JvmField
                val isAudioInput = siteMode != SiteMode.DEFAULT

                val data = data
                val noteID = this@Site.noteID
                val noteIDs = this@Site.noteIDs
                val bundleEntryPath = this@Site.bundleEntryPath
                val title = targetComputing.title
                val artist = targetComputing.artist
                val levelText = targetComputing.levelText
                val level = targetComputing.level
                val wantLevelID = targetComputing.wantLevelID
                val genre = targetComputing.genre
                val bpm = targetComputing.bpm
                val lowestBPM = targetComputing.lowestBPM
                val highestBPM = targetComputing.highestBPM
                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 inputMode = targetComputing.inputMode

                @JvmField
                val isAutoLongNote = targetComputing.isAutoLongNote

                @JvmField
                val isFavorNoteFile = this@Site.isFavorNoteFile

                @JvmField
                val isFavorModeComponent = this@Site.isFavorModeComponent

                @JvmField
                val isFavorAudioMultiplier = this@Site.isFavorAudioMultiplier

                @JvmField
                val isAutoSiteHand = this@Site.isAutoSiteHand

                val validHunterMode = this@Site.validHunterMode
                val validNetMode = this@Site.validNetMode
                val allowedPostableItems = this@Site.allowedPostableItems
                val bundleName = this@Site.bundleName
                val modeComponentData = this@Site.modeComponentData
            })
            data.filter {
                !it.avatarName.startsWith("@")
            }.forEach { siteYell ->
                Translator.translate(avatar.language, siteYell.siteYell, avatar).thenAccept {
                    avatar.send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object {
                        val siteID = this@Site.siteID.toString()
                        val siteYellID = siteYell.siteYellID
                        val siteYell = it
                    })
                }
            }
            if (isEnterQuitAware && avatar.isEnterQuitAware) {
                putSiteYell("@Enter", avatarID, avatar.avatarName, System.currentTimeMillis())
            }
            if (hasAvatar) {
                doCallSiteAvatar(false)
            } else {
                doCallSiteAvatar(avatar, PlatformSystem.platformAvatars)
            }
        }
    }

    fun exileAvatar(avatar: Avatar, toExileAvatar: Avatar): Boolean {
        return synchronized(avatarsCSX) {
            if (siteHand == avatar) {
                if (toExileAvatar.isSU) {
                    avatar.send(
                        EventOuterClass.Event.EventID.NOTIFY_INFO,
                        avatar.translateLanguage("failedExileCompetence")
                    )
                    false
                } else {
                    quitAvatar(toExileAvatar)
                    true
                }
            } else {
                false
            }
        }
    }

    fun setSiteHand(avatar: Avatar, siteHand: Avatar) {
        synchronized(avatarsCSX) {
            if (this.siteHand == avatar) {
                avatarConfigures.replace(siteHand.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT)
                this.siteHand = siteHand
                doCallSiteAvatar(true)
                if (siteHandQueue.remove(siteHand)) {
                    siteHandQueue.offer(siteHand)
                }
            }
        }
    }

    private fun setAutoSiteHand() {
        synchronized(avatarsCSX) {
            siteHandQueue.peek()?.let {
                avatarConfigures.replace(it.avatarID, AvatarConfigure.LEVYING, AvatarConfigure.DEFAULT)
                siteHand = it
                doCallSiteAvatar(true)
                siteHandQueue.offer(it)
            }
        }
    }

    fun setSiteName(avatar: Avatar, siteName: String) {
        synchronized(avatarsCSX) {
            if (siteHand == avatar) {
                this.siteName = siteName
                doCallSiteAvatar(false)
            }
        }
    }

    fun enterSite(avatar: Avatar, siteCipher: String) {
        if (isCallable) {
            synchronized(avatarsCSX) {
                if (!avatar.isSU && avatars.size >= avatarsCeiling) {
                    avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("siteHasNotSeat"))
                } else if (isAvatarJoined(avatar)) {
                    avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("alreadyEnteredSite"))
                } else {
                    if (avatar.isSU || this.siteCipher.isEmpty() || this.siteCipher == siteCipher) {
                        enterSite(avatar)
                    } else {
                        avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongSiteCipher"))
                    }
                }
            }
        }
    }

    fun putSiteYell(siteID: UUID, siteName: String, avatarID: String, avatarName: String, millis: Long) {
        val target = getTarget(avatarID)
        sendSiteYell(JSON.TwilightSiteYell(SiteHandler.defaultSiteID.toString(), synchronized(siteYells) {
            val siteYell = SiteYell(avatarID, "@Invite", millis, ObjectMapper().writeValueAsString(object {
                val avatarName = avatarName
                val siteID = siteID.toString()
                val siteName = siteName
            }), siteYells.size, -1)
            putSiteYell(siteYell)
            siteYell
        }, target))
    }

    fun putSiteYell(tvItem: TVSystem.TVItem) {
        val target = getTarget("")
        val siteYell = synchronized(siteYells) {
            val siteYell = SiteYell(
                "",
                "@TV",
                System.currentTimeMillis(),
                ObjectMapper().writeValueAsString(tvItem),
                siteYells.size,
                -1
            )
            putSiteYell(siteYell)
            siteYell
        }
        sendSiteYell(JSON.TwilightSiteYell(SiteHandler.defaultSiteID.toString(), siteYell, target))
        DB.saveSiteYell(siteID.toString(), siteYell)
    }

    fun putSiteYell(situation: String, avatarID: String, avatarName: String, millis: Long) {
        val target = getTarget(avatarID)
        val siteYell = synchronized(siteYells) {
            val siteYell = SiteYell(avatarID, situation, millis, avatarName, siteYells.size, -1)
            putSiteYell(siteYell)
            siteYell
        }
        sendSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, target))
        if (isEssential) {
            DB.saveSiteYell(siteID.toString(), siteYell)
        }
    }

    fun putSiteYell(avatar: Avatar, qwilightSiteYell: JSON.QwilightSiteYell) {
        synchronized(avatarsCSX) {
            if (isAvatarJoined(avatar)) {
                if (isEditable) {
                    val avatarID = avatar.avatarID
                    val avatarName = avatar.avatarName
                    val millis = System.currentTimeMillis()
                    if (avatar.isMillisSuitable(millis)) {
                        if (qwilightSiteYell.target.isEmpty()) {
                            qwilightSiteYell.target = getTarget(avatarID)
                        }
                        val siteYell = synchronized(siteYells) {
                            val siteYell =
                                SiteYell(
                                    avatarID,
                                    avatarName,
                                    millis,
                                    qwilightSiteYell.siteYell,
                                    siteYells.size,
                                    -1
                                )
                            putSiteYell(siteYell)
                            siteYell
                        }
                        sendTranslatedSiteYell(
                            JSON.TwilightSiteYell(
                                siteID.toString(),
                                siteYell,
                                qwilightSiteYell.target
                            )
                        )
                        if (isEssential) {
                            DB.saveSiteYell(siteID.toString(), siteYell)
                        }
                        if (!avatar.isAwilight) {
                            logInfo("$avatarName ($avatarID): ${siteYell.siteYell}")
                        }
                    } else {
                        avatar.send(
                            EventOuterClass.Event.EventID.SITE_YELL, JSON.TwilightSiteYell(
                                siteID.toString(),
                                SiteYell(
                                    avatarID,
                                    "",
                                    millis,
                                    avatar.translateLanguage("millisNotSuitable"),
                                    -1,
                                    -1
                                ),
                                ""
                            )
                        )
                    }
                }
            }
        }
    }

    fun putSiteYell(avatar: Member, event: MessageReceivedEvent) {
        val avatarID = "$${avatar.id}"
        PlatformSystem.putDrawing(avatarID, avatar.effectiveAvatarUrl).thenAccept {
            if (event.message.contentDisplay.isNotEmpty()) {
                val target = getTarget(avatarID)
                val siteYell = synchronized(siteYells) {
                    val siteYell =
                        SiteYell(
                            avatarID,
                            avatar.effectiveName,
                            System.currentTimeMillis(),
                            event.message.contentDisplay,
                            siteYells.size,
                            event.messageIdLong
                        )
                    putSiteYell(siteYell)
                    siteYell
                }
                sendTranslatedSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, target))
                if (isEssential) {
                    DB.saveSiteYell(siteID.toString(), siteYell)
                }
            }

            event.message.stickers.forEach {
                val target = getTarget(avatarID)
                val siteYell = synchronized(siteYells) {
                    val siteYell =
                        SiteYell(
                            avatarID,
                            avatar.effectiveName,
                            System.currentTimeMillis(),
                            it.iconUrl,
                            siteYells.size,
                            event.messageIdLong
                        )
                    putSiteYell(siteYell)
                    siteYell
                }
                sendTranslatedSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, target))
                if (isEssential) {
                    DB.saveSiteYell(siteID.toString(), siteYell)
                }
            }

            event.message.attachments.forEach {
                val target = getTarget(avatarID)
                val siteYell = synchronized(siteYells) {
                    val siteYell =
                        SiteYell(
                            avatarID,
                            avatar.effectiveName,
                            System.currentTimeMillis(),
                            it.url,
                            siteYells.size,
                            event.messageIdLong
                        )
                    putSiteYell(siteYell)
                    siteYell
                }
                sendTranslatedSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, target))
                if (isEssential) {
                    DB.saveSiteYell(siteID.toString(), siteYell)
                }
            }
        }
    }

    fun doModifySiteYell(avatar: Member, event: MessageUpdateEvent) {
        PlatformSystem.putDrawing("$${avatar.id}", avatar.effectiveAvatarUrl).thenAccept {
            synchronized(siteYells) {
                platformSiteYells.computeIfPresent(event.messageIdLong) { _, platformSiteYell ->
                    platformSiteYell.forEach {
                        it.siteYell = event.message.contentDisplay
                        DB.doModifySiteYell(it)
                        send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object {
                            val siteID = SiteHandler.platformSiteID
                            val siteYellID = it.siteYellID
                            val siteYell = it.siteYell
                        })
                    }
                    platformSiteYell
                }
            }
        }
    }

    fun wipeSiteYell(event: MessageDeleteEvent) {
        synchronized(siteYells) {
            platformSiteYells.computeIfPresent(event.messageIdLong) { _, platformSiteYell ->
                platformSiteYell.forEach {
                    it.isWiped = true
                    DB.wipeSiteYell(it)
                    send(EventOuterClass.Event.EventID.WIPE_SITE_YELL, object {
                        val siteID = SiteHandler.platformSiteID
                        val siteYellID = it.siteYellID
                    })
                }
                null
            }
        }
    }

    fun setNetBundle(bundleName: String) {
        this.bundleName = bundleName
        doCallSiteNet()
    }

    fun getSiteYells(avatar: Avatar, qwilightGetSiteYells: JSON.QwilightGetSiteYells) {
        synchronized(avatarsCSX) {
            if (isAvatarJoined(avatar)) {
                val siteYellID = qwilightGetSiteYells.siteYellID

                val data = synchronized(siteYells) {
                    siteYells.stream().skip(
                        0.coerceAtLeast(siteYellID - SITE_YELL_UNIT).toLong()
                    ).limit(siteYellID.coerceAtMost(SITE_YELL_UNIT).toLong()).map {
                        val avatarName = it.avatarName
                        val isWiped = it.isWiped
                        object {
                            val avatarID = it.avatarID
                            val avatarName = if (isWiped) "@Wiped" else avatarName
                            val date = it.date
                            val siteYell = if (isWiped) avatarName else it.siteYell
                            val siteYellID = it.siteYellID
                        }
                    }.toList()
                }
                avatar.send(EventOuterClass.Event.EventID.GET_SITE_YELLS, object {
                    val siteID = this@Site.siteID.toString()
                    val data = data
                })
                data.filter {
                    !it.avatarName.startsWith("@")
                }.forEach { siteYell ->
                    Translator.translate(avatar.language, siteYell.siteYell, avatar).thenAccept {
                        avatar.send(EventOuterClass.Event.EventID.MODIFY_SITE_YELL, object {
                            val siteID = this@Site.siteID
                            val siteYellID = siteYell.siteYellID
                            val siteYell = it
                        })
                    }
                }
            }
        }
    }

    private fun getTarget(avatarID: String): String {
        synchronized(avatarsCSX) {
            val avatars = avatars.filter { it.isAwilight && it.avatarID != avatarID }
            return if (avatars.isEmpty()) {
                ""
            } else {
                avatars[(Math.random() * avatars.size).toInt()].avatarID
            }
        }
    }

    fun setSiteNotify(siteNotify: String) {
        this.siteNotify = siteNotify
        val siteYell = synchronized(siteYells) {
            val siteYell =
                SiteYell("", "@Notify", System.currentTimeMillis(), siteNotify, siteYells.size, -1)
            putSiteYell(siteYell)
            siteYell
        }
        sendTranslatedSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, ""))
        if (isEssential) {
            DB.saveSiteYell(siteID.toString(), siteYell)
        }
    }

    fun putSIteYell(twilightCommentSiteYell: JSON.TwilightCommentSiteYell) {
        val siteYell = synchronized(siteYells) {
            val siteYell = SiteYell(
                twilightCommentSiteYell.avatarID,
                "@Comment",
                System.currentTimeMillis(),
                ObjectMapper().writeValueAsString(twilightCommentSiteYell),
                siteYells.size,
                -1
            )
            putSiteYell(siteYell)
            siteYell
        }
        sendSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, ""))
        DB.saveSiteYell(siteID.toString(), siteYell)
    }

    fun putSIteYell(twilightAbilitySiteYell: JSON.TwilightAbilitySiteYell) {
        val siteYell = synchronized(siteYells) {
            val siteYell = SiteYell(
                twilightAbilitySiteYell.avatarID,
                "@Ability",
                System.currentTimeMillis(),
                ObjectMapper().writeValueAsString(twilightAbilitySiteYell),
                siteYells.size,
                -1
            )
            putSiteYell(siteYell)
            siteYell
        }
        sendSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, ""))
        DB.saveSiteYell(siteID.toString(), siteYell)
    }

    fun putSIteYell(twilightLevelSiteYell: JSON.TwilightLevelSiteYell) {
        val siteYell = synchronized(siteYells) {
            val siteYell = SiteYell(
                twilightLevelSiteYell.avatarID,
                "@Level",
                System.currentTimeMillis(),
                ObjectMapper().writeValueAsString(twilightLevelSiteYell),
                siteYells.size,
                -1
            )
            putSiteYell(siteYell)
            siteYell
        }
        sendSiteYell(JSON.TwilightSiteYell(siteID.toString(), siteYell, ""))
        DB.saveSiteYell(siteID.toString(), siteYell)
    }

    fun setModeComponent(avatar: Avatar, modeComponentData: Any) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                setModeComponent(modeComponentData)
            }
        }
    }

    fun setModeComponent(modeComponentData: Any) {
        this.modeComponentData = modeComponentData
        doCallSiteModeComponent()
    }

    fun setFavorNoteFile(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                isFavorNoteFile = !isFavorNoteFile
                doCallSiteNet()
                doCallSiteAvatar(true)
            }
        }
    }

    fun setFavorModeComponent(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                isFavorModeComponent = !isFavorModeComponent
                doCallSiteNet()
                doCallSiteModeComponent()
            }
        }
    }

    fun setFavorAudioMultiplier(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                isFavorAudioMultiplier = !isFavorAudioMultiplier
                if (isFavorAudioMultiplier) {
                    validNetMode = 0
                }
                doCallSiteNet()
            }
        }
    }

    fun setAvatarGroup(avatar: Avatar, qwilightSetAvatarGroup: JSON.QwilightSetAvatarGroup) {
        val avatarGroup = qwilightSetAvatarGroup.avatarGroup
        if (avatarGroup in 0..4) {
            synchronized(avatarsCSX) {
                if (siteMode == SiteMode.NET && siteSituation == SiteSituation.DEFAULT) {
                    avatarGroups[avatar.avatarID] = qwilightSetAvatarGroup.avatarGroup
                    doCallSiteAvatar(false)
                }
            }
        }
    }

    fun setAutoSiteHand(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar) {
                isAutoSiteHand = !isAutoSiteHand
                doCallSiteNet()
            }
        }
    }

    fun setValidHunterMode(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                validHunterMode = (validHunterMode + 1) % 3
                doCallSiteNet()
            }
        }
    }

    fun setValidNetMode(avatar: Avatar, qwilightSetValidNetMode: JSON.QwilightSetValidNetMode) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                validNetMode = qwilightSetValidNetMode.validNetMode
                if (validNetMode > 0) {
                    isFavorAudioMultiplier = false
                }
                doCallSiteNet()
            }
        }
    }

    fun setAllowedPostableItems(allowedPostableItems: Array<Int>) {
        this.allowedPostableItems = allowedPostableItems
        doCallSiteNet()
    }

    fun setAllowedPostableItems(
        avatar: Avatar,
        qwilightSetAllowedPostableItems: JSON.QwilightSetAllowedPostableItems
    ) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                setAllowedPostableItems(qwilightSetAllowedPostableItems.allowedPostableItems)
            }
        }
    }

    fun doCallNetSiteComments(avatar: Avatar) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && isAvatarJoined(avatar)) {
                if (netSIteCommentItems.isEmpty()) {
                    avatar.send(
                        EventOuterClass.Event.EventID.WARNING,
                        avatar.translateLanguage("netSiteCommentNotAvailable")
                    )
                } else {
                    avatar.send(
                        EventOuterClass.Event.EventID.CALL_NET_SITE_COMMENTS, netSIteCommentItems.stream().map {
                            object {
                                val date = it.date
                                val data = setValidMap(it.netItems).map { netItem ->
                                    object {
                                        val avatarNetStatus = netItem.avatarNetStatus.number
                                        val avatarID = netItem.avatarID
                                        val avatarName = netItem.avatarName
                                        val stand = netItem.stand
                                        val band = netItem.highestBand
                                        val point = netItem.point
                                        val highestJudgment = netItem.comment?.highestJudgment ?: 0
                                        val higherJudgment = netItem.comment?.higherJudgment ?: 0
                                        val highJudgment = netItem.comment?.highJudgment ?: 0
                                        val lowJudgment = netItem.comment?.lowJudgment ?: 0
                                        val lowerJudgment = netItem.comment?.lowerJudgment ?: 0
                                        val lowestJudgment = netItem.comment?.lowestJudgment ?: 0
                                    }
                                }.toArray()
                            }
                        }.toArray()
                    )
                }
            }
        }
    }

    fun setNoteFileContents(avatar: Avatar, noteFileData: JSON.QwilightSetNoteFile) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && siteHand == avatar && siteSituation == SiteSituation.DEFAULT) {
                if (Arrays.compare(noteFileData.noteIDs, noteIDs) != 0) {
                    synchronized(avatarsCSX) {
                        avatars.forEach {
                            avatarConfigures[it.avatarID] = AvatarConfigure.DEFAULT
                        }
                    }
                    doCallSiteAvatar(false)
                }
                setNoteFileContents(noteFileData)
            }
        }
    }

    fun setNoteFileContents(noteFileData: JSON.QwilightSetNoteFile) {
        noteID = noteFileData.noteID
        noteIDs = noteFileData.noteIDs
        bundleEntryPath = noteFileData.bundleEntryPath
        targetComputing.title = noteFileData.title
        targetComputing.artist = noteFileData.artist
        targetComputing.genre = noteFileData.genre
        targetComputing.levelText = noteFileData.levelText
        targetComputing.level = noteFileData.level
        targetComputing.wantLevelID = noteFileData.wantLevelID
        targetComputing.bpm = noteFileData.bpm
        targetComputing.lowestBPM = noteFileData.lowestBPM
        targetComputing.highestBPM = noteFileData.highestBPM
        targetComputing.judgmentStage = noteFileData.judgmentStage
        targetComputing.hitPointsValue = noteFileData.hitPointsValue
        targetComputing.totalNotes = noteFileData.totalNotes
        targetComputing.longNotes = noteFileData.longNotes
        targetComputing.autoableNotes = noteFileData.autoableNotes
        targetComputing.trapNotes = noteFileData.trapNotes
        targetComputing.highestInputCount = noteFileData.highestInputCount
        targetComputing.length = noteFileData.length
        targetComputing.isAutoLongNote = noteFileData.isAutoLongNote
        targetComputing.inputMode = noteFileData.inputMode
        doCallSiteNet()
    }

    fun setSiteYells(siteYells: Collection<SiteYell>) {
        synchronized(siteYells) {
            siteYells.forEach {
                putSiteYell(it)
            }
        }
    }

    private fun putSiteYell(siteYell: SiteYell) {
        this.siteYells.add(siteYell)
        val platformID = siteYell.platformID
        if (platformID != -1L) {
            platformSiteYells.computeIfAbsent(platformID) {
                mutableListOf()
            }.add(siteYell)
        }
    }

    fun silentSite() {
        synchronized(siteYells) {
            siteYells.clear()
        }
    }

    fun audioInput(avatar: Avatar, data: ByteString) {
        synchronized(avatarsCSX) {
            if (siteMode != SiteMode.DEFAULT && isAvatarJoined(avatar)) {
                val avatarID = avatar.avatarID
                val event = EventOuterClass.Event.newBuilder().apply {
                    eventID = EventOuterClass.Event.EventID.AUDIO_INPUT
                    twilightAudioInput = EventOuterClass.Event.TwilightAudioInput.newBuilder().apply {
                        siteID = this@Site.siteID.toString()
                        this.avatarID = avatarID
                    }.build()
                    addData(data)
                }.build()
                avatars.filter { it.avatarID != avatarID }.forEach {
                    it.send(event)
                }
                lastAudioInputMillis[avatarID] = System.currentTimeMillis()
            }
        }
    }

    fun postItem(avatar: Avatar, qwilightPostItem: EventOuterClass.Event.QwilightPostItem) {
        synchronized(avatarsCSX) {
            if (siteMode == SiteMode.NET && isAvatarJoined(avatar) && handlerID == qwilightPostItem.handlerID && siteSituation == SiteSituation.NET && netItems[avatar.avatarID]?.avatarNetStatus == EventOuterClass.Event.AvatarNetStatus.Default) {
                val avatarGroup = avatarGroups[avatar.avatarID]
                avatars.filter {
                    val isPositive = qwilightPostItem.isPositive
                    if (isPositive == -1) {
                        true
                    } else {
                        if (avatar == it) {
                            isPositive > 0
                        } else {
                            (isPositive > 0) xor (avatarGroups[it.avatarID] == 0 || avatarGroups[it.avatarID] != avatarGroup)
                        }
                    }
                }.forEach {
                    it.send(EventOuterClass.Event.newBuilder().apply {
                        eventID = EventOuterClass.Event.EventID.POST_ITEM
                        twilightPostItem = EventOuterClass.Event.TwilightPostItem.newBuilder().apply {
                            handlerID = this@Site.handlerID
                            avatarName = avatar.avatarName
                            postedItem = qwilightPostItem.postedItem
                            wait = Random.nextDouble(qwilightPostItem.lowestWait, qwilightPostItem.highestWait)
                        }.build()
                    }.build())
                }
            }
        }
    }

    override fun toString(): String {
        return when (siteSituation) {
            SiteSituation.DEFAULT -> "[$siteID] $siteName [${avatars.size}] Idle"
            SiteSituation.COMPILING -> "[$siteID] $siteName [${avatars.size}] Compiling Note File"
            SiteSituation.NET -> "[$siteID] $siteName [${avatars.size}] Net Computing"
        }
    }

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

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

    companion object {
        const val SITE_YELL_UNIT = 16

        private val ses: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor {
            Executors.defaultThreadFactory().newThread(it).apply {
                isDaemon = true
            }
        }
    }
}