package mg.dot.feufaro.musicXML

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.modules.SerializersModule
import mg.dot.feufaro.FileRepository
import nl.adaptivity.xmlutil.serialization.XML
import java.util.Locale.getDefault
import kotlin.math.max

class MusicXML(private val fileRepository: FileRepository) {
    val module = SerializersModule {
    }

    val xml = XML (module) {
        defaultPolicy {
            ignoreUnknownChildren()
        }
    }
    var dollarMarkers: MutableList<String> = mutableListOf()
    var xmlString = ""
    fun processDirection(dir: MXDirection) {
        dir.directionType.forEach { dirType ->
            if (dirType.words != null) {
                val daCapo = dirType.words!!.replace(".", "")
                if (daCapo.startsWith("DC") || daCapo.startsWith("DS") || daCapo.lowercase(getDefault()).startsWith("rit")) {
                    dollarMarkers += daCapo
                }
            }
            if (dirType.segno != null) {
                dollarMarkers += "$"
            }
            if (dirType.wedge != null) {
                when (dirType.wedge!!.type) {
                    "crescendo" -> dollarMarkers += "<"
                    "diminuendo" -> dollarMarkers += ">"
                    "stop" -> dollarMarkers += "="
                }
            }
            if (dirType.dynamics.isNotEmpty()) {
                dirType.dynamics.forEach { it ->
                    when {
                        it.p != null -> dollarMarkers += "p"
                        it.pp != null -> dollarMarkers += "pp"
                        it.ppp != null -> dollarMarkers += "ppp"
                        it.pppp != null -> dollarMarkers += "pppp"
                        it.ppppp != null -> dollarMarkers += "ppppp"
                        it.pppppp != null -> dollarMarkers += "pppppp"
                        it.f != null -> dollarMarkers += "f"
                        it.ff != null -> dollarMarkers += "ff"
                        it.fff != null -> dollarMarkers += "fff"
                        it.ffff != null -> dollarMarkers += "ffff"
                        it.fffff != null -> dollarMarkers += "fffff"
                        it.ffffff != null -> dollarMarkers += "ffffff"
                        it.mp != null -> dollarMarkers += "mp"
                        it.mf != null -> dollarMarkers += "mf"
                        it.sf != null -> dollarMarkers += "sf"
                        it.sfp != null -> dollarMarkers += "sfp"
                        it.sfpp != null -> dollarMarkers += "sfpp"
                        it.fp != null -> dollarMarkers += "fp"
                        it.rf != null -> dollarMarkers += "rf"
                        it.rfz != null -> dollarMarkers += "rfz"
                        it.sfz != null -> dollarMarkers += "sfz"
                        it.sffz != null -> dollarMarkers += "sffz"
                        it.fz != null -> dollarMarkers += "fz"
                        it.n != null -> dollarMarkers += "n"
                        it.sfzp != null -> dollarMarkers += "sfzp"
                    }

                }
            }
            if (dirType.bracket?.type in listOf("start", "stop")) {
                when (dirType.bracket!!.type) {
                    "start" -> dollarMarkers += "B["
                    "stop" -> dollarMarkers += "B]"
                }
            }
        }
    }
    suspend fun load() {
        val xmlContent = fileRepository.readFileContent("assets://ews-89.xml")
        val xslContent = fileRepository.readFileContent("assets://timepart.xsl")
        xmlString = performXsltTransformation(xmlContent, xslContent)
        val solfaXML = SolfaXML()
        try {
            val xmlObject = xml.decodeFromString<MXScorePartwise>(xmlString)
            var keyFifths = 6
            var measureLength = 0
            var timeIndex = 0
            var tupletType = ' '
            var inTupletCount = 0
            var voiceAlter = 0
            var lastChordDuration = 0
            var timePointer = 0
            var maxTimePointer = 0
            var numPart = 0
            var firstMeasureLength = 0
            var workTitle = xmlObject.work?.workTitle
            if (workTitle == null) {
                workTitle = xmlObject.work?.workNumber
            }
            val creator = xmlObject.identification?.creator
            var lyricist = ""
            var composer = ""
            creator?.map { it ->
                if (it.type == "lyricist") {
                    lyricist = it.content ?: ""
                }
                if (it.type == "composer") {
                    composer = it.content ?: ""
                }
            }
            val credits = xmlObject.credit.filter { credit ->
                credit.creditType == "composer"
            }
            if (credits.isNotEmpty() && composer == "") {
                composer = credits[0].creditWords?.content ?: ""
            }
            solfaXML.setMeta("t", workTitle)
            solfaXML.setMeta("h", composer)
            solfaXML.setMeta("a", lyricist)
            xmlObject.part.forEach { partNode ->
                numPart ++
                maxTimePointer = 0
                var lastTimeCursor = 0
                measureLength = 0
                partNode.measure.forEach { measureNode ->
                    /*if (measureNode.backup?.duration != null) {
                        val backupDuration = measureNode.backup!!.duration!!
                        timeIndex -= backupDuration
                    }*/
                    timePointer = maxTimePointer
                    measureLength = max(measureLength, timePointer - lastTimeCursor)
                    if (lastTimeCursor == 0 && measureLength != 0) {
                        firstMeasureLength = measureLength
                    }
                    lastTimeCursor = timePointer
                    if (measureNode.attributes?.key?.fifths != null) {
                        keyFifths = measureNode.attributes!!.key!!.fifths!!
                    }
                    solfaXML.setKeyFifths(keyFifths)
                    //val keySign = measureNode.attributes?.clef?.sign
                    val numerator = measureNode.attributes?.time?.beats
                    val denominator = measureNode.attributes?.time?.beatType
                    solfaXML.setMeasure(numerator, denominator)
                    measureNode.note.forEach { noteNode ->
                        // avec un xlst, il faut changer les <backup><duration> en <note><backup-duration>1</...
                        // val stem = noteNode.stem Stem up ou down ou double n'est pas suffisant pour distinguer voix 1 / voix 2
                        if (noteNode.direction != null) {
                            processDirection(noteNode.direction!!)
                            return@forEach
                        }
                        if (noteNode.backupDuration != null) {
                            timePointer -= noteNode.backupDuration!!
                            return@forEach
                        }
                        // et <forward><duration> en <note><forward-duration>1</...
                        if (noteNode.forwardDuration != null) {
                            timePointer += noteNode.forwardDuration!!
                            return@forEach
                        }
                        if ((noteNode.grace != null) || (noteNode.cue != null)) {
                            // On va ignorer les grace et les cue notes.
                            return@forEach
                        }
                        val duration = noteNode.duration // 8 === type==half
                        val voiceNumber = 2 - ((noteNode.voice ?: 1) % 2)
                        val chord = noteNode.chord // !null = accord (same note)
                        if (chord == null) {
                            voiceAlter = 0
                        } else {
                            timePointer -= lastChordDuration
                            voiceAlter ++
                        }
                        lastChordDuration = duration ?: 0
                        val beam = if (noteNode.beam?.number == 1) noteNode.beam else null // ligature (begin/continue/end pour ( et )
                        val timeModification = noteNode.timeModification
                        if (timeModification != null) {
                            if (timeModification.actualNotes == 3 && timeModification.normalNotes == 2) {
                                if (inTupletCount == 0) {
                                    inTupletCount += timeModification.actualNotes
                                    solfaXML.setTuplet(true)
                                }
                                inTupletCount--
                            }
                        }
                        val nextNote = when {
                            noteNode.unpitched != null -> "z"
                            noteNode.rest != null -> "z"
                            noteNode.pitch != null -> noteNode.pitch!!.toKey(solfaXML.currentKey, numPart)
                            else -> "z"
                        }
                        solfaXML.addNote(voiceNumber+voiceAlter, timePointer, duration ?: 0, nextNote, numPart, beam, dollarMarkers)
                        noteNode.lyric.forEach {
                            solfaXML.addLyrics(timePointer, it)
                        } // todo number/syllabic/text
                        dollarMarkers = mutableListOf()
                        /*noteNode.notations?.forEach {notationsNode ->
                        }*/
                        timePointer += duration ?: 0
                        if (maxTimePointer < timePointer) {
                            maxTimePointer = timePointer
                        }
                    }
                }
            }
            solfaXML.toSolfa(measureLength, firstMeasureLength)
        } catch(e: Exception) {
            println("${e.message}")
        }
    }

    init {
        val parseScope = CoroutineScope(Dispatchers.Default)
        parseScope.launch {
            if (false)
            load()
        }
    }
}