package net.taehui.twilight

import net.taehui.twilight.awilight.DefaultCompute
import net.taehui.twilight.note.*
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.util.*
import kotlin.math.abs
import kotlin.math.floor

class BMSCompiler(noteFileData: ByteArray, format: String) : BaseCompiler(noteFileData, format) {
    private open class BMSInputItem(val inputNote: InputNote, val bmsPosition: Double) : Comparable<BMSInputItem> {
        override fun compareTo(other: BMSInputItem): Int {
            return inputNote.compareTo(other.inputNote)
        }
    }

    private class BMSLongInputItem(inputNote: InputNote, bmsPosition: Double, val bmsID: String) :
        BMSInputItem(inputNote, bmsPosition)

    private open class EarlyBMSInputItem(val bmsPosition: Double) : Comparable<EarlyBMSInputItem> {
        override fun compareTo(other: EarlyBMSInputItem): Int {
            return bmsPosition.compareTo(other.bmsPosition)
        }
    }

    private class EarlyBMSLongInputItem(bmsPosition: Double, val bmsID: String) : EarlyBMSInputItem(bmsPosition)

    private val lines = mutableListOf<String>()
    private val bmsPositionLogicalYMap = TreeMap<Double, Double>()
    private val bmsPositionBPMMap = TreeMap<Double, Double>()
    private val bmsPositionMultiplierMap = TreeMap<Double, Double>()
    private val bmsIDStopMap = mutableMapOf<String, Double>()
    private val bmsIDMultiplierMap = mutableMapOf<String, Double>()
    private var longNoteBMSID = ""
    private var highestMeter = 0
    private var is4K = false
    private var is6K = false

    override fun handleCompile(targetComputing: Computing) {
        val bmsPositionBPMMap = TreeMap<Double, Double>()
        val meterMeterMultiplierMap = LinkedHashMap<Int, Double>()
        meterMeterMultiplierMap[-1] = 1.0
        val bmsIDBPMMap = mutableMapOf<String, Double>()
        BufferedReader(InputStreamReader(ByteArrayInputStream(noteFileData), format)).use {
            val saltComputer = Random()
            var lastBin = 0
            var isValidStatement = true
            for (line in it.lines()) {
                if (line.startsWith("#")) {
                    val lintAt1 = line.substring(1)
                    val delimited = lintAt1.split(":", " ", ".", ";", limit = 2).toTypedArray()
                    val property = delimited[0]
                    if (isValidStatement) {
                        if (delimited.size > 1) {
                            val data = delimited[1].trim()
                            if (property.equals("RANDOM", true)) {
                                val rValue = delimited[1].toInt()
                                lastBin = 1 + saltComputer.nextInt(data.toInt())
                                if (rValue > 1) {
                                    targetComputing.isSalt = true
                                }
                                continue
                            } else if (property.equals("IF", true)) {
                                isValidStatement = lastBin == data.toInt()
                                continue
                            }
                        }
                        lines.add(line)
                    } else {
                        if (property.equals("ELSE", true) || property.equals("ENDIF", true) || property.equals(
                                "IFEND",
                                true
                            )
                        ) {
                            isValidStatement = true
                        }
                    }
                }
            }
        }
        for (line in lines) {
            val lineAt1 = line.substring(1)
            val delimited = lineAt1.split(":", " ", ".", ";", limit = 2).toTypedArray()
            if (isMainBMSData(lineAt1)) {
                var property = if (delimited.size > 1) delimited[0] else delimited[0].substring(0, 5)
                property = property.padStart(5, '0')
                val data = if (delimited.size > 1) delimited[1] else delimited[0].substring(5)
                val meter = property.substring(0, 3).toInt()
                highestMeter = meter.coerceAtLeast(highestMeter)
                if (property[3] == '0' && property[4] == '2') {
                    meterMeterMultiplierMap[meter] = data.toDouble()
                }
            } else {
                val property = delimited[0].trim()
                if (delimited.size > 1) {
                    val data = delimited[1].trim()
                    if (property.startsWith("BPM", true)) {
                        if (property.length > 3) {
                            bmsIDBPMMap[property.substring(3, 5)] = data.toDouble()
                        } else {
                            try {
                                targetComputing.levyingBPM = data.toDouble()
                            } catch (e: NumberFormatException) {
                                val delimitedData = data.split(" ")
                                if (delimitedData.size > 1) {
                                    bmsIDBPMMap[delimitedData[0]] = delimitedData[1].toDouble()
                                } else {
                                    targetComputing.levyingBPM = data.split("-").toTypedArray()[0].toDouble()
                                }
                            }
                        }
                    } else if (property.equals("LNOBJ", true)) {
                        longNoteBMSID = data
                    } else if (property.equals("LNMODE", true)) {
                        when (data.toInt()) {
                            1 -> targetComputing.isAutoLongNote = true
                            2, 3 -> targetComputing.isAutoLongNote = false
                        }
                    }
                } else {
                    if (property.equals("4K", true)) {
                        is4K = true
                    } else if (property.equals("6K", true)) {
                        is6K = true
                    }
                }
            }
        }
        val earlyBMSInputItemSet = mutableMapOf<String, SortedSet<EarlyBMSInputItem>>()
        val earlyBMSLongInputItemSet = mutableMapOf<String, SortedSet<EarlyBMSLongInputItem>>()
        valueComponent = Component(targetComputing.levyingBPM)
        val bmsPositionSet = TreeSet<Double>()
        var title = ""
        var titleAssister = ""
        var artist = ""
        var artistAssister = ""
        val audioTargets = mutableListOf<String>()
        val audioValues = mutableSetOf<String>()
        for (line in lines) {
            val lintAt1 = line.substring(1)
            val delimited = lintAt1.split(":", " ", ".", ";", limit = 2).toTypedArray()
            if (isMainBMSData(lintAt1)) {
                var property = if (delimited.size > 1) delimited[0] else delimited[0].substring(0, 5)
                property = property.padStart(5, '0')
                val noteVariety0 = property[3]
                val noteVariety1 = property[4]
                val data = if (delimited.size > 1) delimited[1] else delimited[0].substring(5)
                val dataCount = data.length - data.length % 2
                val meter = property.substring(0, 3).toInt()
                var meterCount = 0
                while (meterCount < dataCount) {
                    val dataValue = data.substring(meterCount, meterCount + 2)
                    if (dataValue != "00") {
                        val bmsPosition = meterCount.toDouble() / dataCount + meter
                        val rawInput = property.substring(3, 5)
                        when (noteVariety0) {
                            '0' -> {
                                when (noteVariety1) {
                                    '1', '4', '6', '7' -> {
                                        bmsPositionSet.add(bmsPosition)
                                        highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                                    }

                                    '3', '8', '9' -> bmsPositionSet.add(bmsPosition)
                                }
                            }

                            '1', '2' -> {
                                if (noteVariety1 != '7') {
                                    if (longNoteBMSID.isNotEmpty()) {
                                        targetComputing.totalNotes += 1
                                        if (rawInput[1] == '6') {
                                            targetComputing.autoableNotes += 1
                                        }
                                        positionStandNoteCountMap[bmsPosition] =
                                            positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                                        highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                                    } else {
                                        earlyBMSLongInputItemSet.getOrPut(rawInput) {
                                            TreeSet()
                                        }.add(EarlyBMSLongInputItem(bmsPosition, dataValue))
                                    }
                                }
                                bmsPositionSet.add(bmsPosition)
                            }

                            '3', '4', '7', '9' -> bmsPositionSet.add(bmsPosition)
                            '5', '6' -> {
                                if (noteVariety1 != '7') {
                                    if (longNoteBMSID.equals(
                                            dataValue,
                                            ignoreCase = true
                                        )
                                    ) {
                                        earlyBMSLongInputItemSet.getOrPut(rawInput) {
                                            TreeSet()
                                        }.add(EarlyBMSLongInputItem(bmsPosition, dataValue))
                                    } else {
                                        earlyBMSInputItemSet.getOrPut(rawInput) {
                                            TreeSet()
                                        }.add(EarlyBMSInputItem(bmsPosition))
                                    }
                                    bmsPositionSet.add(bmsPosition)
                                }
                            }

                            'D', 'E' -> {
                                if (noteVariety1 != '7') {
                                    targetComputing.trapNotes += 1
                                    highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                                    bmsPositionSet.add(bmsPosition)
                                }
                            }

                            'S' -> {
                                if (noteVariety1 == 'C') {
                                    bmsPositionSet.add(bmsPosition)
                                }
                            }
                        }
                    }
                    meterCount += 2
                }
            } else {
                val property = delimited[0]
                if (delimited.size > 1) {
                    val data = delimited[1].trim()
                    if (property.startsWith("WAV", true)) {
                        if (data.isNotEmpty()) {
                            audioTargets.add(property.substring(3, 5))
                        }
                    } else if (property.startsWith("STOP", true)) {
                        bmsIDStopMap[property.substring(4, 6)] = data.toDouble()
                    } else if (property.startsWith("SCROLL", true)) {
                        bmsIDMultiplierMap[property.substring(6, 8)] = data.toDouble()
                    } else if (property.equals("TITLE", true)) {
                        title = data
                    } else if (property.equals("SUBTITLE", true)) {
                        titleAssister = data
                    } else if (property.equals("ARTIST", true)) {
                        artist = data
                    } else if (property.equals("SUBARTIST", true)) {
                        artistAssister = data
                    } else if (property.equals("GENRE", true)) {
                        targetComputing.genre = data
                    } else if (property.equals("PLAYLEVEL", true)) {
                        var levelTextValue = data.toDoubleOrNull()
                        if (levelTextValue != null) {
                            levelTextValue = abs(floor(levelTextValue))
                            if (levelTextValue < 100) {
                                targetComputing.levelText = "LV. $data"
                            } else {
                                val levelText = levelTextValue.toString()
                                targetComputing.levelText = "LV. ${
                                    levelText.substring(
                                        0.coerceAtLeast(levelText.indexOf('.') - 2),
                                        levelText.indexOf('.')
                                    )
                                }"
                            }
                        } else {
                            targetComputing.levelText = data
                        }
                    } else if (property.equals("DIFFICULTY", true)) {
                        if (data.isNotEmpty()) {
                            targetComputing.level = 1.coerceAtLeast(data.toInt()).coerceAtMost(5)
                        }
                    } else if (property.equals("RANK", true)) {
                        try {
                            when (0.coerceAtLeast(data.toInt().coerceAtMost(4))) {
                                0 -> targetComputing.judgmentStage = 10.0
                                1 -> targetComputing.judgmentStage = 7.0
                                2 -> targetComputing.judgmentStage = 5.0
                                3 -> targetComputing.judgmentStage = 3.0
                                4 -> targetComputing.judgmentStage = 0.0
                            }
                        } catch (_: NumberFormatException) {
                        }
                    }
                }
            }
        }
        for ((rawInput, earlyBMSLongInputItems) in earlyBMSLongInputItemSet) {
            var lastEarlyBMSLongInputItem: EarlyBMSLongInputItem? = null
            for (earlyBMSLongInputItem in earlyBMSLongInputItems) {
                if (earlyBMSLongInputItem.bmsID.equals(longNoteBMSID, ignoreCase = true)) {
                    if (lastEarlyBMSLongInputItem != null) {
                        targetComputing.longNotes += 1
                        targetComputing.totalNotes += 2
                        if (rawInput[1] == '6') {
                            targetComputing.autoableNotes += 2
                        }
                        var bmsPosition = lastEarlyBMSLongInputItem.bmsPosition
                        highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                        positionStandNoteCountMap[bmsPosition] =
                            positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                        bmsPosition = earlyBMSLongInputItem.bmsPosition
                        highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                        positionStandNoteCountMap[bmsPosition] =
                            positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                        lastEarlyBMSLongInputItem = null
                        continue
                    }
                } else {
                    if (lastEarlyBMSLongInputItem != null) {
                        targetComputing.totalNotes += 1
                        if (rawInput[1] == '6') {
                            targetComputing.autoableNotes += 1
                        }
                        val bmsPosition = lastEarlyBMSLongInputItem.bmsPosition
                        highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                        positionStandNoteCountMap[bmsPosition] =
                            positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                    }
                }
                lastEarlyBMSLongInputItem = earlyBMSLongInputItem
            }
            if (lastEarlyBMSLongInputItem != null) {
                targetComputing.totalNotes += 1
                if (rawInput[1] == '6') {
                    targetComputing.autoableNotes += 1
                }
                val bmsPosition = lastEarlyBMSLongInputItem.bmsPosition
                highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                positionStandNoteCountMap[bmsPosition] = positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
            }
        }
        for ((rawInput, earlyBMSInputItems) in earlyBMSInputItemSet) {
            var lastEarlyBMSInputItem: EarlyBMSInputItem? = null
            for (earlyBMSInputItem in earlyBMSInputItems) {
                if (lastEarlyBMSInputItem != null) {
                    targetComputing.longNotes += 1
                    targetComputing.totalNotes += 2
                    if (rawInput[1] == '6') {
                        targetComputing.autoableNotes += 2
                    }
                    var bmsPosition = lastEarlyBMSInputItem.bmsPosition
                    highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                    positionStandNoteCountMap[bmsPosition] = positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                    bmsPosition = earlyBMSInputItem.bmsPosition
                    highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                    positionStandNoteCountMap[bmsPosition] = positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
                    lastEarlyBMSInputItem = null
                } else {
                    lastEarlyBMSInputItem = earlyBMSInputItem
                }
            }
            if (lastEarlyBMSInputItem != null) {
                targetComputing.totalNotes += 1
                if (rawInput[1] == '6') {
                    targetComputing.autoableNotes += 1
                }
                val bmsPosition = lastEarlyBMSInputItem.bmsPosition
                highestPosition = highestPosition.coerceAtLeast(bmsPosition)
                positionStandNoteCountMap[bmsPosition] = positionStandNoteCountMap.getOrDefault(bmsPosition, 0) + 1
            }
        }
        targetComputing.title =
            if (titleAssister.isEmpty() || title.endsWith(titleAssister)) title else "$title $titleAssister"
        targetComputing.artist = if (artistAssister.isEmpty()) artist else "$artist / $artistAssister"
        val inputSet = mutableSetOf<Int>()
        for (line in lines) {
            val lintAt1 = line.substring(1)
            val delimited = lintAt1.split(":", " ", ".", ";", limit = 2).toTypedArray()
            if (isMainBMSData(lintAt1)) {
                var property = if (delimited.size > 1) delimited[0] else delimited[0].substring(0, 5)
                property = property.padStart(5, '0')
                val data = if (delimited.size > 1) delimited[1] else delimited[0].substring(5)
                val dataCount = data.length - data.length % 2
                val meter = property.substring(0, 3).toInt()
                var meterCount = 0
                while (meterCount < dataCount) {
                    val mData = data.substring(meterCount, meterCount + 2)
                    if (mData != "00") {
                        val bmsPosition = meterCount.toDouble() / dataCount + meter
                        val noteVariety0 = property[3]
                        val noteVariety1 = property[4]
                        when (noteVariety0) {
                            '0' -> {
                                when (noteVariety1) {
                                    '3' -> bpmValues[bmsPosition] = mData.toInt(16).toDouble()
                                    '8' -> {
                                        bmsIDBPMMap[mData]?.let {
                                            bpmValues[bmsPosition] = it
                                        }
                                    }

                                    '9' -> {
                                        bmsIDStopMap[mData]?.let {
                                            bmsPositionBPMMap[bmsPosition] = it
                                        }
                                    }
                                }
                            }

                            '1', '2', '5', '6' -> {
                                if (audioTargets.contains(mData)) {
                                    audioValues.add(mData)
                                }
                            }

                            'S' -> {
                                if (noteVariety1 == 'C') {
                                    bmsIDMultiplierMap[mData]?.let {
                                        this.bmsPositionMultiplierMap[bmsPosition] = it
                                    }
                                }
                            }
                        }
                        inputSet.add(getBMSInput(noteVariety1, noteVariety0, Component.InputMode.INPUT_MODE_14_2))
                    }
                    meterCount += 2
                }
            } else {
                if (delimited.size > 1) {
                    val data = delimited[1].trim()
                    if (delimited[0].equals("TOTAL", ignoreCase = true)) {
                        try {
                            targetComputing.hitPointsValue =
                                0.0.coerceAtLeast(data.toDouble()) / (100 * targetComputing.totalNotes)
                        } catch (_: NumberFormatException) {
                        }
                    }
                }
            }
        }
        targetComputing.inputMode = getInputMode(inputSet, Utility.getDataID(targetComputing.noteID))
        for (i in 0..highestMeter + 1) {
            bmsPositionSet.add(i.toDouble())
        }

        var lastBMSPosition = 0.0
        var lastLogicalY = 0.0
        var lastWait = 0.0
        var lastMultiplierValue = 1.0
        var lastBPM = targetComputing.levyingBPM
        for (bmsPosition in bmsPositionSet) {
            val meterMultiplier = meterMeterMultiplierMap.getOrDefault(floor(lastBMSPosition).toInt(), 1.0)
            lastLogicalY -= (bmsPosition - lastBMSPosition) * lastMultiplierValue * meterMultiplier * valueComponent.logicalYMeter
            bmsPositionLogicalYMap[bmsPosition] = lastLogicalY
            this.bmsPositionMultiplierMap[bmsPosition]?.let {
                lastMultiplierValue = it
            }
            lastWait += valueComponent.millisMeter * meterMultiplier * (bmsPosition - lastBMSPosition)
            bpmValues[bmsPosition]?.let {
                lastBPM = it
                valueComponent.setBPM(it)
            }
            waitValues[bmsPosition] = lastWait
            lastBMSPosition = bmsPosition
            bmsPositionBPMMap[bmsPosition]?.let {
                lastWait += valueComponent.millisMeter * it / Component.STANDARD_METER
                this.bmsPositionBPMMap[bmsPosition] = lastBPM
            }
        }
        targetComputing.isBanned = audioValues.size < 2 || targetComputing.totalNotes == 0
        targetComputing.noteVariety = Component.NoteVariety.BMS
    }

    override fun handleCompile(defaultComputer: DefaultCompute) {
        handleCompile(defaultComputer as Computing)
        for (i in highestMeter + 1 downTo 0) {
            val logicalY = valueComponent.levyingHeight + bmsPositionLogicalYMap[i.toDouble()]!!
            val wait = waitValues[i.toDouble()]!!
            notes.add(MeterNote(logicalY, wait))
        }
        val inputCount = Component.INPUT_COUNTS[defaultComputer.inputMode.value]
        val bmsInputItemSets = mutableListOf<TreeSet<BMSInputItem>>()
        for (i in inputCount downTo 0) {
            bmsInputItemSets.add(TreeSet())
        }
        val bmsLongInputItemSets = mutableListOf<TreeSet<BMSLongInputItem>>()
        for (i in inputCount downTo 0) {
            bmsLongInputItemSets.add(TreeSet())
        }
        for (line in lines) {
            val lintAt1 = line.substring(1)
            if (isMainBMSData(lintAt1)) {
                val delimited = lintAt1.split(":", " ", ".", ";", limit = 2).toTypedArray()
                var property = if (delimited.size > 1) delimited[0] else delimited[0].substring(0, 5)
                property = property.padStart(5, '0')
                val noteVariety0 = property[3]
                val noteVariety1 = property[4]
                if (noteVariety0 != '0' || noteVariety1 != '2') {
                    val data = if (delimited.size > 1) delimited[1] else delimited[0].substring(5)
                    val dataCount = data.length - data.length % 2
                    val meter = property.substring(0, 3).toInt()
                    var meterCount = 0
                    while (meterCount < dataCount) {
                        val dataValue = data.substring(meterCount, meterCount + 2)
                        if (dataValue != "00") {
                            val input = getBMSInput(noteVariety1, noteVariety0, defaultComputer.inputMode)
                            val bmsPosition = meterCount.toDouble() / dataCount + meter
                            val logicalY = valueComponent.levyingHeight + bmsPositionLogicalYMap[bmsPosition]!!
                            val wait = waitValues[bmsPosition]!!
                            when (noteVariety0) {
                                '0' -> {
                                    when (noteVariety1) {
                                        '1' -> defaultComputer.waitAudioNoteMap.add(wait)
                                        '3', '8' -> {
                                            bpmValues[bmsPosition]?.let {
                                                waitBPMMap[wait] = it
                                            }
                                        }

                                        '4', '6', '7' -> defaultComputer.waitMediaNoteMap.add(wait)
                                        '9' -> {
                                            bmsPositionBPMMap[bmsPosition]?.let {
                                                valueComponent.setBPM(it)
                                                waitStopMap[wait] = 0.0
                                                waitStopMap[wait + valueComponent.millisMeter * bmsIDStopMap[dataValue]!! / Component.STANDARD_METER] =
                                                    it
                                            }
                                        }
                                    }
                                }

                                '1', '2' -> {
                                    if (input != 0) {
                                        val inputNote = InputNote(logicalY, wait, input)
                                        if (longNoteBMSID.isEmpty()) {
                                            notes.add(inputNote)
                                        } else {
                                            bmsLongInputItemSets[input].add(
                                                BMSLongInputItem(
                                                    inputNote,
                                                    bmsPosition,
                                                    dataValue
                                                )
                                            )
                                        }
                                    }
                                }

                                '3', '4' -> {
                                    if (input != 0) {
                                        notes.add(VoidNote(logicalY, wait, input))
                                    }
                                }

                                '5', '6' -> {
                                    if (input != 0) {
                                        val inputNote = InputNote(logicalY, wait, input)
                                        if (longNoteBMSID.equals(
                                                dataValue,
                                                ignoreCase = true
                                            )
                                        ) {
                                            bmsLongInputItemSets[input].add(
                                                BMSLongInputItem(
                                                    inputNote,
                                                    bmsPosition,
                                                    dataValue
                                                )
                                            )
                                        } else {
                                            bmsInputItemSets[input].add(BMSInputItem(inputNote, bmsPosition))
                                        }
                                    }
                                }

                                'D', 'E' -> {
                                    if (input != 0) {
                                        notes.add(TrapNote(logicalY, wait, input))
                                    }
                                }

                                'S' -> {
                                    if (noteVariety1 == 'C') {
                                        bmsPositionMultiplierMap[bmsPosition]?.let {
                                            waitMultiplierMap[wait] = it
                                        }
                                    }
                                }
                            }
                        }
                        meterCount += 2
                    }
                }
            }
        }
        for (i in inputCount downTo 1) {
            var lastBMSLongInputItem: BMSLongInputItem? = null
            for (bmsLongInputItem in bmsLongInputItemSets[i]) {
                if (bmsLongInputItem.bmsID.equals(longNoteBMSID, ignoreCase = true)) {
                    if (lastBMSLongInputItem != null) {
                        val lastInputNote = lastBMSLongInputItem.inputNote
                        val lastWait = lastInputNote.wait
                        val targetWait = bmsLongInputItem.inputNote.wait
                        notes.removeAll(notes.filter {
                            it.wait in lastWait..targetWait && it.input == i
                        }.toSet())
                        notes.add(
                            LongNote(
                                lastInputNote.logicalY,
                                lastWait,
                                lastInputNote.input,
                                targetWait - lastWait,
                                valueComponent.levyingHeight + bmsPositionLogicalYMap[lastBMSLongInputItem.bmsPosition]!! - bmsLongInputItem.inputNote.logicalY
                            )
                        )
                        lastBMSLongInputItem = null
                        continue
                    }
                } else {
                    if (lastBMSLongInputItem != null) {
                        notes.add(lastBMSLongInputItem.inputNote)
                    }
                }
                lastBMSLongInputItem = bmsLongInputItem
            }
            if (lastBMSLongInputItem != null) {
                notes.add(lastBMSLongInputItem.inputNote)
            }
        }
        for (i in inputCount downTo 1) {
            var lastBMSInputItem: BMSInputItem? = null
            for (bmsInputItem in bmsInputItemSets[i]) {
                lastBMSInputItem = if (lastBMSInputItem != null) {
                    val lastInputNote = lastBMSInputItem.inputNote
                    val lastWait = lastInputNote.wait
                    val targetWait = bmsInputItem.inputNote.wait
                    notes.removeAll(notes.filter {
                        it.wait in lastWait..targetWait && it.input == i
                    }.toSet())
                    notes.add(
                        LongNote(
                            lastInputNote.logicalY,
                            lastWait,
                            lastInputNote.input,
                            targetWait - lastWait,
                            valueComponent.levyingHeight + bmsPositionLogicalYMap[lastBMSInputItem.bmsPosition]!! - bmsInputItem.inputNote.logicalY
                        )
                    )
                    null
                } else {
                    bmsInputItem
                }
            }
            if (lastBMSInputItem != null) {
                notes.add(lastBMSInputItem.inputNote)
            }
        }
    }

    private fun getInputMode(inputSet: Collection<Int>, dataID: Int): Component.InputMode {
        if (dataID == 1) {
            return Component.InputMode.INPUT_MODE_9
        }
        if (is4K) {
            return Component.InputMode.INPUT_MODE_4
        }
        if (is6K) {
            return Component.InputMode.INPUT_MODE_6
        }
        if (inputSet.all { it == 0 }) {
            return Component.InputMode.INPUT_MODE_5_1
        }
        val isMode71 = inputSet.contains(7) || inputSet.contains(8)
        val isMode102 =
            inputSet.contains(9) || inputSet.contains(10) || inputSet.contains(11) || inputSet.contains(12) || inputSet.contains(
                13
            )
        val isMode142 = isMode71 && isMode102 || inputSet.contains(14) || inputSet.contains(15)
        if (isMode142) {
            return Component.InputMode.INPUT_MODE_14_2
        }
        if (isMode102) {
            return Component.InputMode.INPUT_MODE_10_2
        }
        if (isMode71) {
            return Component.InputMode.INPUT_MODE_7_1
        }
        return Component.InputMode.INPUT_MODE_5_1
    }

    private fun getBMSInput(noteVariety0: Char, noteVariety1: Char, inputMode: Component.InputMode): Int {
        when (noteVariety1) {
            '1', '3', '5', 'D' -> {
                when (inputMode) {
                    Component.InputMode.INPUT_MODE_4 -> {
                        when (noteVariety0) {
                            '1', '2' -> {
                                return noteVariety0.code - '0'.code
                            }

                            '4', '5' -> {
                                return noteVariety0.code - '1'.code
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_5_1, Component.InputMode.INPUT_MODE_10_2 -> {
                        when (noteVariety0) {
                            '1', '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code + 1
                            }

                            '6' -> {
                                return 1
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_6 -> {
                        when (noteVariety0) {
                            '1', '2', '3' -> {
                                return noteVariety0.code - '0'.code
                            }

                            '5' -> {
                                return noteVariety0.code - '1'.code
                            }

                            '8', '9' -> {
                                return noteVariety0.code - '3'.code
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_7_1, Component.InputMode.INPUT_MODE_14_2 -> {
                        when (noteVariety0) {
                            '1', '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code + 1
                            }

                            '6' -> {
                                return 1
                            }

                            '8', '9' -> {
                                return noteVariety0.code - '1'.code
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_9 -> {
                        when (noteVariety0) {
                            '1', '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code
                            }
                        }
                    }

                    else -> Unit
                }
            }

            '2', '4', '6', 'E' -> {
                when (inputMode) {
                    Component.InputMode.INPUT_MODE_10_2 -> {
                        when (noteVariety0) {
                            '1', '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code + 6
                            }

                            '6' -> {
                                return 12
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_14_2 -> {
                        when (noteVariety0) {
                            '1', '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code + 8
                            }

                            '6' -> {
                                return 16
                            }

                            '8', '9' -> {
                                return noteVariety0.code - '2'.code + 8
                            }
                        }
                    }

                    Component.InputMode.INPUT_MODE_9 -> {
                        when (noteVariety0) {
                            '2', '3', '4', '5' -> {
                                return noteVariety0.code - '0'.code + 4
                            }
                        }
                    }

                    else -> Unit
                }
            }
        }
        return 0
    }

    companion object {
        fun isMainBMSData(lintAt1: String): Boolean {
            return if (lintAt1.length >= 5) {
                val mainContent3 = lintAt1[3]
                val mainContent4 = lintAt1[4]
                Character.isDigit(lintAt1[0]) && Character.isDigit(lintAt1[1]) && Character.isDigit(
                    lintAt1[2]
                ) && (Character.isDigit(mainContent3) || (mainContent3 == 'D' || mainContent3 == 'E') && Character.isDigit(
                    mainContent4
                ) || mainContent3 == 'S' && mainContent4 == 'C')
            } else {
                false
            }
        }
    }
}