package mg.dot.feufaro.musicXML

class SolfaXML {
    private val metaC = listOf("Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "Gb", "Db")
    private var meta: MutableMap<String, String> = mutableMapOf()
    var tuo: MutableList<SolfaXMLTuo> = mutableListOf()
    var currentKey = "C"
    var curBeam: Pair<Int, MXBeam?> = Pair(-1,null)
    var curMarkers: MutableList<String> = mutableListOf()
    var startTuplet = false
    var N: MutableList<String> = mutableListOf()
    fun setMeta(key: String, value: String?) {
        if (value != null && value != "") {
            meta[key] = value
        }
    }
    fun setTuplet(seton: Boolean = true) {
        startTuplet = seton
    }
    fun setKeyFifths(keyFifths: Int) {
        currentKey = metaC.getOrElse(keyFifths+6) { "C" }
        if (!meta.containsKey("c")) {
            meta["c"] = currentKey
        }
    }
    fun setMeasure(numerator: Int?, denominator: Int?) {
        if ((meta.getOrDefault("m", null) != null) && numerator == null) {
            return
        }
        var measure1 = numerator ?: 4
        var measure2 = denominator ?: 4
        if (measure2 == 2) {
            measure1 *= 2
            measure2 *= 2
        }
        meta["m"] = "$measure1/$measure2"
    }
    fun newTuo(newTimeLapse: Int, newDuration: Int): SolfaXMLTuo {
        val new = SolfaXMLTuo().setTime(newTimeLapse, newDuration)
        addTuo(new)
        return new
    }
    fun applyBeam(tuo: SolfaXMLTuo) {
        if (curBeam.second != null) {
            if (curBeam.second?.content == "begin") {
                tuo.addMarker(curBeam.first, "(")
            }
            if (curBeam.second?.content == "end") {
                tuo.addMarker(curBeam.first, ")")
            }
            if (startTuplet) {
                tuo.addMarker(curBeam.first, "t")
                startTuplet = false
            }
            curBeam = Pair(-1, null)
        }
        if (curMarkers.isNotEmpty()) {
            curMarkers.forEach {
                tuo.addMarker(curBeam.first,"\\$\\{$it\\}")
            }
            curMarkers = mutableListOf()
        }
    }
    fun addTuo(newTuo: SolfaXMLTuo) {
        applyBeam(newTuo)
        tuo.add(newTuo)
    }
    fun editTuoNote(tuo: SolfaXMLTuo, voiceNumber: Int, note: String) {
        tuo.setNote(voiceNumber, note)
        applyBeam(tuo)
    }
    fun addNote(voiceNumberIn: Int, timePointer: Int, duration: Int, note: String, numPart: Int? = 1, beam: MXBeam? = null, dollarMarkers: MutableList<String> = mutableListOf()) {
        if (duration == 0) {
            return
        }
        val voiceNumber = 2 * ((numPart ?: 1) - 1) + (2 - (voiceNumberIn % 2))
        curBeam = Pair(voiceNumber, beam)
        if (curBeam.second != null) {

        }
        curMarkers = dollarMarkers
        val tuoEdit: SolfaXMLTuo = tuo.find { foundTuo ->
            timePointer >= foundTuo.timeLapse && timePointer < (foundTuo.timeLapse + foundTuo.duration)
        } ?: newTuo(timePointer, duration)
        val lapse1 = tuoEdit.timeLapse
        val lapse2 = timePointer
        val lapse3 = timePointer + duration
        val lapse4 = tuoEdit.timeLapse + tuoEdit.duration
        val lapse5 = lapse3
        val duration1 = lapse2 - lapse1
        val duration2 = lapse3 - lapse2
        val duration3 = lapse4 - lapse3
        val duration4 = lapse5 - lapse4

        if (duration1 > 0) {
            val newTuo = tuoEdit.clone(lapse1, duration1)
            addTuo(newTuo)
            tuoEdit.resetMarkers()
        }
        if (duration2 > 0) {
            if (duration3 > 0) {
                tuoEdit.setTime(lapse2, duration2)
            }
            if (duration1 > 0) {
                tuoEdit.oneNote.forEachIndexed { itVoice, itNote ->
                    editTuoNote(tuoEdit, itVoice, "-")
                }
            }
            editTuoNote(tuoEdit, voiceNumber, note)
        }
        if (duration3 > 0) {
            val newTuo = tuoEdit.clone(lapse3, duration3, newNote = "-")
            newTuo.resetMarkers()
            addTuo(newTuo)
        }
        if (duration4 > 0) {
            //println("addNote recursion")
            addNote(voiceNumber, lapse4, duration4, "-", numPart)
        }
    }
    fun addLyrics(timePointer: Int, lyric: MXLyric) {
        val tuoEdit: SolfaXMLTuo? = tuo.find { foundTuo ->
            timePointer == foundTuo.timeLapse
        }
        tuoEdit?.addLyrics(lyric)
    }
    fun toSolfa(measureLength: Int, firstMeasureLength: Int) {
        val nbVoice = (tuo.getOrNull(0)?.oneNote?.size ?: 1) - 1
        val voiceOrders: MutableList<VoiceOrder> = MutableList(nbVoice / 2) { i -> VoiceOrder(2*i+1) }
        val metaString = "M0:|" + meta.map { (key, value) ->
            "$key:" + value.replace('|', '!')
        }.joinToString(separator = "|")
        if (tuo.isEmpty()) {
            println("TUO Empty")
            return
        }
        var uString = "U0:z"
        val measure = meta.getOrElse("m") { "4/4"}
        val numerator = measure.replace(Regex("/.*"), "").toIntOrNull() ?: 0
        val multiplier = 4 * numerator / measureLength
        val blanksAtStart = 4 * numerator - (firstMeasureLength * multiplier)
        val letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVW"
        if (blanksAtStart >= 0 && blanksAtStart < 32) {
            uString += letters[blanksAtStart]
        }
        uString += ":"

        println(metaString)
        var curTimeLapse = 0
        val tuoIterator = tuo.iterator()
        while (tuoIterator.hasNext()) {
            val currentTuo = tuoIterator.next()
            var tuoSelect: SolfaXMLTuo? = currentTuo
            while (currentTuo.timeLapse >= curTimeLapse) {
                if (currentTuo.timeLapse > curTimeLapse) {
                    tuoSelect = tuo.find { foundTuo ->
                        foundTuo.timeLapse == curTimeLapse
                    }
                    if (tuoSelect == null) {
                        curTimeLapse ++
                        break
                    }
                } else {
                    tuoSelect = currentTuo
                }
                if (tuoSelect!!.duration * multiplier in (0..32)) {
                    uString += tuoSelect.toUString()+letters[tuoSelect.duration * multiplier]
                    voiceOrders.forEachIndexed { index, voiceOrder ->
                        val newVoiceOrder = tuoSelect.getN(voiceOrder)
                        voiceOrders[index] = newVoiceOrder
                        while (N.size <= newVoiceOrder.voiceMin + 2) {
                            N.add("")
                        }
                        val marker1Before = if (newVoiceOrder.markerOne == ")") "" else newVoiceOrder.markerOne
                        val marker1After  = if (newVoiceOrder.markerOne == ")") ")" else ""
                        val marker2Before = if (newVoiceOrder.markerTwo == ")") "" else newVoiceOrder.markerOne
                        val marker2After  = if (newVoiceOrder.markerTwo == ")") ")" else ""
                        N[newVoiceOrder.voiceMin]   += marker1Before + newVoiceOrder.noteOne + marker1After
                        N[newVoiceOrder.voiceMin+1] += marker2Before + newVoiceOrder.noteTwo + marker2After
                    }
                }
                curTimeLapse += tuoSelect.duration
            }
        }
        println(uString)
        println(N
            .mapIndexed { index, value ->
                val newValue = value
                    .replace("₄", ",,,,")
                    .replace("₃", ",,,")
                    .replace("₂", ",,")
                    .replace("₁", ",")
                    .replace("¹", "'")
                    .replace("²", "''")
                    .replace("³", "'''")
                    .replace("⁴", "''''")
                "N$index:$newValue"
            }
            .filter{it.last() != ':'}
            .joinToString(separator = "\n"))

    }
}

data class VoiceOrder(
    val voiceMin: Int = 1,
    val voiceFirst: Int = 0,
    val noteOne: String = "",
    val noteTwo: String = "",
    val markerOne: String = "",
    val markerTwo: String = ""
)